🗄️ MySQL 事务 & 隔离级别:小白也能懂
转账为什么必须用事务?ACID 是什么?脏读/幻读又是啥?四种隔离级别分别解决了什么、怎么解决,一篇讲透。
什么是事务?为什么需要它
🧾 事务 = 最小逻辑工作单元
事务(Transaction)是数据库执行的最小逻辑工作单元,是一组不可分割的 SQL 操作。
要么全部成功(提交 COMMIT),要么全部失败(回滚 ROLLBACK),不会出现部分生效。
🏦 经典例子:银行转账
A 账户扣 100,B 账户加 100。两个操作必须在一个事务里,任何一个失败都要回滚全部。
否则 A 扣了钱、B 没到账,账就平不了了。
事务通过 ACID 四个特性来保证数据的可靠性和一致性。
ACID 四特性
⚛️ A · 原子性 Atomicity
事务里的操作要么全部成功,要么全部不执行。
中途出错,已执行的部分也回滚到事务开始前。
InnoDB 通过 undo log 实现——回滚时用它撤销已做的修改。
🎯 C · 一致性 Consistency
事务执行前后,数据库都处在一致状态。
所有约束(主键、外键、唯一约束、CHECK)都要满足。
一致性是最终目标,A、I、D 都是保证一致性的手段。
🧊 I · 隔离性 Isolation
并发事务之间互不干扰,每个事务感觉不到其他事务在并发执行。
InnoDB 用锁机制 + MVCC实现不同隔离级别,在隔离性和性能之间权衡。
💾 D · 持久性 Durability
事务提交成功后修改就是永久的,即使系统崩溃也不丢失。
InnoDB 通过 redo log 实现——提交时把 redo log 刷盘,崩溃恢复时重放已提交的修改。
一致性是目标
A 原子性、I 隔离性、D 持久性,最终都是为了保证 C 一致性。
undo log 管原子性
回滚靠 undo log 撤销已做的修改。
redo log 管持久性
崩溃恢复靠 redo log 重放已提交的修改。
锁 + MVCC 管隔离性
隔离级别越高越安全,但并发越低,要按业务权衡。
四个隔离级别:解决了什么、怎么解决的
| 隔离级别 | 能解决 | 遗留问题 |
|---|---|---|
| 读未提交 READ UNCOMMITTED | — | 脏读、不可重复读、幻读 |
| 读已提交 READ COMMITTED | 脏读 | 不可重复读、幻读 |
| 可重复读 REPEATABLE READ | 脏读、不可重复读 | 幻读(InnoDB 已基本解决) |
| 串行化 SERIALIZABLE | 脏读、不可重复读、幻读 | — |
👀 ① 读未提交 READ UNCOMMITTED
完全不隔离,能读到别人还没提交的数据。
一个事务还没提交,另一个事务就能读到它改的值。
→ 脏读、不可重复读、幻读全都有。隔离级别最低,也最危险,几乎不用。
✅ ② 读已提交 READ COMMITTED
解决脏读:靠 MVCC 快照——每次读都生成一个新快照,只读已提交的版本,所以读不到别人未提交的脏数据。
但每次读都生成新快照,两次读之间别人提交了修改,第二次读就会变。
→ 还有不可重复读、幻读。Oracle/PostgreSQL 的默认级别。
📸 ③ 可重复读 REPEATABLE READ
解决不可重复读:靠 MVCC 快照——事务开始时生成一个快照,整个事务都读这一个,所以同一行两次读结果一样。
再加间隙锁/临键锁阻止范围插入,基本解决幻读。
→ 脏读、不可重复读都解决了。MySQL 的默认级别。
🔒 ④ 串行化 SERIALIZABLE
解决所有问题:所有读操作都加锁,事务完全串行执行,一个跑完另一个才跑。
→ 脏读、不可重复读、幻读全解决,但并发性能最低,几乎不用。
一句话串起来:隔离级别从低到高,就是「越来越严格」——读未提交什么都不管,读已提交管住脏读,可重复读再管住不可重复读,串行化全管住。
MySQL InnoDB 默认是「可重复读」,和 Oracle/PostgreSQL 默认「读已提交」不同,这是 MySQL 的一个重要区别。
三个并发问题:脏读 / 不可重复读 / 幻读
🧼 ① 脏读
读到别人还没提交的数据。
事务 A 改了数据还没提交,事务 B 读到了——如果 A 回滚,B 读到的就是「脏数据」。
读未提交才会发生
🔁 ② 不可重复读
同一事务里两次读同一行,结果不一样。
事务 A 先读到 balance=1000,期间事务 B 提交把 balance 改成 900,A 再读变成 900。
读已提交才会发生
👻 ③ 幻读
同一事务里两次查询,结果集行数不一样(多了/少了行)。
事务 A 查「age>20 的人」,第一次 3 行,期间 B 插入一行 25 岁的,A 再查变成 4 行——像幻觉。
可重复读理论上会有
InnoDB 怎么在可重复读下避免幻读
📸 快照读(普通 SELECT)
通过 MVCC 版本链,事务内多次读取都从同一个快照取数据。
看不到其他事务新插入的行 → 避免幻读。
这就是「可重复读」的实现核心。
🔒 当前读(UPDATE / FOR UPDATE)
通过 间隙锁(Gap Lock)/ 临键锁(Next-Key Lock)锁定范围。
阻止其他事务在该范围内插入新行 → 也避免幻读。
快照读
普通 SELECT
MVCC 版本链
同一快照, 看不到新插入行
当前读
SELECT ... FOR UPDATE / UPDATE / DELETE
间隙锁 / 临键锁
锁范围, 阻止插入
追求更高并发
对一致性要求不高时,可改成 READ COMMITTED(SET GLOBAL transaction_isolation='READ-COMMITTED')。
SERIALIZABLE 几乎不用
性能最低,全串行,除非要求极严格一致性否则不用。
MyISAM 不支持事务
MySQL 事务只适用于 InnoDB 引擎。
大事务要避免
长时间运行的大事务会锁大量资源,尽量保持事务简短。
事务是数据库不可分割的一组 SQL(要么全成要么全回滚),靠 ACID 保证:原子性(undo log)、一致性(最终目标)、隔离性(锁+MVCC)、持久性(redo log)。MySQL InnoDB 默认「可重复读」,靠 MVCC 快照读 + 间隙锁基本避免了脏读/不可重复读/幻读,比 Oracle/PostgreSQL 的默认「读已提交」更高。
事务是数据库执行的最小逻辑工作单元,是一组不可分割的 SQL 操作,要么全部成功提交,要么全部失败回滚。ACID 四个特性:A 原子性,要么全做要么全不做,InnoDB 靠 undo log 回滚;C 一致性,事务前后数据库都处在一致状态,所有约束满足,这是最终目标;I 隔离性,并发事务互不干扰,靠锁和 MVCC 实现;D 持久性,提交后修改就是永久的,靠 redo log 崩溃恢复。
四种,从低到高:读未提交、读已提交、可重复读、串行化。MySQL InnoDB 默认是可重复读 REPEATABLE READ,这跟 Oracle、PostgreSQL 默认读已提交不一样。读未提交会有脏读;读已提交解决了脏读但还有不可重复读和幻读;可重复读基本都解决了;串行化全解决但性能最低,几乎不用。
脏读是读到别人还没提交的数据,如果对方回滚了,我读到的就是假数据,只有读未提交会发生。不可重复读是同一事务里两次读同一行结果不一样,因为中间别的已提交事务改了这行,读已提交会发生。幻读是同一事务里两次查询结果行数不一样,多了或少了行,比如查年龄大于 20 的人第一次 3 个,别人插入一个 25 岁的我再查变 4 个,像幻觉,可重复读理论上会有。