引言
从并发问题出发,彻底理解 MySQL 为什么这样设计事务隔离
一、为什么需要事务隔离级别?
在并发数据库系统中,多个事务同时读写同一份数据是常态。如果不加任何控制,就会引发各种数据一致性问题,例如:
- 一个事务读到了另一个事务尚未提交的数据
- 同一事务中,两次查询结果不一致
- 查询过程中"凭空多出"一些记录
这些问题统称为并发读问题 ,而 事务隔离级别(Isolation Level),正是数据库在「性能」和「一致性」之间做出的不同权衡方案。
二、MySQL 支持的四种事务隔离级别
SQL 标准定义了四种隔离级别,MySQL(InnoDB)全部支持。
读未提交(Read Uncommitted)
特点
- 一个事务可以读到另一个事务尚未提交的数据
问题
- 脏读(Dirty Read)
示例
事务A:update account set balance = 0 where id = 1; (未提交)
事务B:select balance from account where id = 1; → 读到 0
事务A:rollback;
B 读到的数据是从未真正存在过的
评价
- 几乎不加任何隔离
- 实际生产环境基本不用
读已提交(Read Committed)
Oracle 的默认隔离级别
特点
- 只能读到已提交的数据
- 每次查询都会读取最新已提交版本
解决的问题
- 避免脏读
仍然存在的问题
- 不可重复读(Non-repeatable Read)
示例
事务A:select balance → 100
事务B:update balance = 200; commit;
事务A:select balance → 200
同一个事务中,两次读取结果不同
可重复读(Repeatable Read)⭐
MySQL InnoDB 默认隔离级别
特点
- 同一个事务中,多次读取同一行记录结果一致
- 基于 MVCC(多版本并发控制)
能解决的问题
- 脏读
- 不可重复读
可重复读下,事务 A 提交的数据,事务 B 能看到吗?
分两种情况:
情况一:普通快照读(一致性读)
select * from table;
- 看不到
- 使用事务开始时生成的 Read View
情况二:当前读(锁定读)
select * from table for update;
select * from table lock in share mode;
- 可以看到
- 直接读取最新版本
这正是 MySQL 可重复读与其他数据库的核心区别之一
串行化(Serializable)
特点
- 所有事务串行执行
- 强制加锁(行锁 / 表锁)
实现方式
- 一种实现方式:读操作也加行级锁
- 完全避免并发问题
缺点
- 并发性能极差
- 几乎不用于高并发系统
三、可重复读真的解决了幻读吗?
什么是幻读(Phantom Read)?
事务A:select count(*) from orders where price > 100;
事务B:insert into orders values (..., price=200); commit;
事务A:再次 select count(*) → 结果变多
多出"幻影行"
MySQL 的"特殊之处"
严格来说:
可重复读 + MVCC 并不能完全解决幻读
- MVCC 只能保证已存在记录的版本一致
- 对于 新插入的记录,MVCC 无能为力
MySQL 如何"防止幻读"?
方案一:锁定读(推荐)
select * from orders where price > 100 for update;
- 会加 Next-Key Lock(记录锁 + 间隙锁)
- 锁住一个范围
- 阻止其他事务插入新记录
从结果上看:幻读被避免
方案二:串行化隔离级别
- 本质是用锁解决
- 代价太高
四、MVCC:多版本并发控制的核心原理
MySQL 高并发性能的关键
MVCC 解决了什么问题?
- 避免读写冲突
- 提高并发性能
- 实现 非阻塞读
MVCC 的核心组件
(1)隐藏字段
-
trx_id:最后一次修改该行的事务 ID -
roll_pointer:指向 undo log
(2)Undo Log
-
保存数据的历史版本
-
用于事务回滚 & MVCC
(3)Read View
- 决定"当前事务能看到哪些版本"
一致性读流程(简化)
当前事务 → 生成 Read View
→ 判断记录 trx_id 是否可见
→ 不可见 → 通过 undo log 找历史版本
MVCC + 锁的协作关系
| 场景 | 机制 |
|---|---|
| 普通 select | MVCC |
| select for updateUpdate | 锁 |
| update / delete | 锁 |
| 防止幻读 | Next-Key Lock |
MVCC 解决"读一致性",锁解决"写冲突"
总结
MySQL 默认使用 可重复读隔离级别
MVCC 保证了:
- 非阻塞读
- 高并发性能
幻读并不是完全由 MVCC 解决
锁定读 + Next-Key Lock 才是幻读的真正终结者
MySQL 的事务模型,本质是:
MVCC + 锁机制的精妙组合