本节摘要:事务把多个操作打包成"要么全做要么全不做"的原子单元。本节讲事务的 ACID 特性、COMMIT/ROLLBACK 用法、隔离级别。
阅读完本节,你应当能够:
转账场景:A 给 B 转 100 元,要扣 A 的余额、加 B 的余额两步。如果扣完 A 的余额后系统崩了,B 没收到——钱凭空消失。事务保证这两步"要么都成功要么都回滚",不会出现中间状态。
BEGIN; UPDATE Accounts SET Balance = Balance - 100 WHERE User = 'A'; UPDATE Accounts SET Balance = Balance + 100 WHERE User = 'B'; COMMIT; -- 两步都成功才提交 -- 若中间出错:ROLLBACK; 两步都撤销

BEGIN; -- 开启事务 INSERT INTO Orders ...; UPDATE Accounts ...; COMMIT; -- 提交,所有变更持久化 -- 或出错时 BEGIN; INSERT INTO Orders ...; -- 发现错误 ROLLBACK; -- 回滚,所有变更撤销
很多 DBMS 默认 autocommit(每条语句自动提交),要多个语句作为一个事务需显式 BEGIN。
并发事务可能互相干扰,产生脏读、不可重复读、幻读等问题。隔离级别控制干扰程度:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 性能 |
|---|---|---|---|---|
| 读未提交 | 可能 | 可能 | 可能 | 最快 |
| 读已提交 | 避免 | 可能 | 可能 | 快 |
| 可重复读 | 避免 | 避免 | 可能 | 中 |
| 串行化 | 避免 | 避免 | 避免 | 最慢 |
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
级别越高隔离越强但并发越低。多数场景用默认(读已提交或可重复读)即可。
事务持有锁,长事务长时间持锁影响并发,甚至导致死锁。原则:
⚠️ 常见坑:开了事务忘提交(长事务),长时间持锁阻塞别人;或事务里做耗时操作拖慢系统。事务要短小快提交。
💡 关键直觉:事务保证"要么全做要么全不做",靠 ACID。隔离级别权衡隔离性和性能,多数用默认。事务要短小快提交,别在里头做耗时操作。
下一节讲用户和权限管理——谁能做什么。
Q1:事务到底解决什么问题?
让"多个操作"变成"一个原子动作":要么全部成功,要么全部回滚。没有事务,转账就是"扣款成功、入账失败"的灾难。事务是数据库保证数据一致性的核心机制,凡是涉及"多步写操作"的业务都应该用事务。
Q2:四个隔离级别到底差在哪?
从低到高:读未提交(可读脏数据)到读已提交(默认,读不到未提交的,但不可重复读)到可重复读(MySQL 默认,同一事务内重复读结果一致,仍可能有幻读)到串行化(完全隔离,最慢)。级别越高隔离越好、并发越低。大多数场景用数据库默认即可,别随意调高。
Q3:什么是脏读、不可重复读、幻读?
脏读:读到别人未提交又回滚的数据;不可重复读:同一事务内两次读同一行,值被别的事务改了;幻读:同一事务内两次范围查询,行数被别的事务插入/删除了。理解这三个问题就能理解隔离级别的意义。
Q4:事务一定要手动 COMMIT 吗?
取决于数据库默认。MySQL 默认 autocommit=1(每条语句自动提交),所以多条语句组成事务要显式 BEGIN...COMMIT;PostgreSQL 默认在事务块内需显式 COMMIT。无论哪种,明确提交时机、及时提交/回滚都是好习惯。
Q5:长事务有什么危害?
持有锁时间过长,阻塞其他事务;占用连接和事务日志;回滚代价大。生产事故常见"开了事务忘了提交"。原则:事务要短、要快、不在事务里做耗时操作(网络请求、大批量查询)。
Q6:死锁怎么产生的?
两个事务互相持有对方需要的锁,各自等待,谁也无法继续。数据库会自动检测并回滚其中一个(牺牲者)。避免:固定加锁顺序、事务尽量短、减少锁范围。死锁报错后重试即可,不必恐慌。