事务是什么
事务把多条语句打包成一个原子操作,要么全部生效,要么全部不生效。MySQL 里用 START TRANSACTION 或 BEGIN 开始,PostgreSQL 用 BEGIN;提交用 COMMIT,撤销用 ROLLBACK。InnoDB 与 PostgreSQL 默认都开启自动提交,单条语句自成一个事务,只有显式开始事务后多条语句才成组。工程上最关注的是原子性与隔离性:前者保证不出现一半成功一半失败,后者决定并发事务之间能看到什么。而长事务是所有数据库都讨厌的东西,它会长时间持有锁与版本信息。
基本信息
| 作用 | 把多条语句打包为原子操作,整体提交或整体回滚 |
|---|---|
| MySQL 开始 | START TRANSACTION 或 BEGIN,需要 InnoDB 引擎 |
| PostgreSQL 开始 | BEGIN 或 START TRANSACTION |
| 提交与回滚 | COMMIT 提交,ROLLBACK 撤销 |
| 自动提交 | 默认 autocommit 打开,单条语句自成一个事务 |
| 保存点 | SAVEPOINT 与 ROLLBACK TO 可用于局部回滚 |
典型使用场景
- 转账与扣减库存:两处变更必须同时成功或同时失败
- 订单主表加明细:一组写入包在一个事务里,避免主表有记录而明细缺失
- 批次清理:删除与归档成对执行,中途失败整体回滚
- 批量导入:多条写语句攒够一次提交,吞吐明显高于逐条提交
- 配合锁读实现先查后改:SELECT … FOR UPDATE 必须在事务内才有意义
- 数据修复:变更前先开事务试跑,确认结果再提交
基本写法
基本写法
-- MySQL 与 PostgreSQL 通用写法
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1; # 转出
UPDATE accounts SET balance = balance + 100 WHERE id = 2; # 转入
-- 两处余额都改对了再提交,否则整体撤销
COMMIT; -- 出错时执行 ROLLBACK;
-- 希望部分回滚时用保存点
SAVEPOINT sp1;
UPDATE accounts SET balance = balance - 1 WHERE id = 3;
ROLLBACK TO SAVEPOINT sp1; # 只撤销保存点之后的操作关键语句对照
| 语句 | MySQL | PostgreSQL |
|---|---|---|
| 开始事务 | START TRANSACTION / BEGIN | BEGIN / START TRANSACTION |
| 提交 | COMMIT | COMMIT |
| 回滚 | ROLLBACK | ROLLBACK |
| 保存点 | SAVEPOINT 与 ROLLBACK TO | SAVEPOINT 与 ROLLBACK TO |
| 自动提交开关 | SET autocommit = 0 | SET autocommit = off |
| 只读事务 | START TRANSACTION READ ONLY | BEGIN READ ONLY |
MySQL 里执行 DDL 会隐式提交当前事务,PostgreSQL 支持在事务里执行 DDL;把建表改表与数据变更混在一个事务里时要特别小心。
事务里不要夹网络调用或大批量计算:长事务会长期占用 undo 与锁资源,还会阻塞 DDL 与后台清理,表现为磁盘膨胀和并发变慢。
同一个业务操作必须在同一个数据库连接里完成:连接池分配到不同连接就是不同事务,锁与快照都不再共享。
MySQL 的 MyISAM 引擎不支持事务与行锁,只有 InnoDB 才具备事务能力;换引擎或做表结构变更前先确认这一点。
事务与并发实践
- 事务粒度尽量小:只包必要的写语句,把查询与外部调用挪到事务外
- 更新顺序保持一致:多个事务按同样顺序改同一批行,能明显减少死锁
- 捕获死锁并重试:数据库会牺牲其中一个事务(MySQL 1213、PostgreSQL 40P01),应用层要有重试逻辑
- 批量写入一次提交:几千行一次 COMMIT 吞吐远高于逐行提交,但要控制单批大小
- 只读事务可以显式声明:部分数据库允许只读事务以减少一致性维护开销
- 事务对象不要跨请求复用:事务的生命周期必须与数据库连接绑定
- 监控长事务:定期查当前运行的事务,超过阈值就告警排查
事务与连接池要成对设计:从池里借出连接后开启事务,提交或回滚之后再归还;中途归还连接会让事务悬空,产生难以复现的数据问题。