可重复读(Repeatable Read)能解决幻读吗?
一、问题背景
在数据库事务中,常见的并发问题有:
- 脏读(Dirty Read):读到未提交的数据。
- 不可重复读(Non-Repeatable Read):两次读取同一行数据结果不一致。
- 幻读(Phantom Read):同一事务中两次查询的结果集行数不一致。
幻读定义:
在一个事务中,多次执行相同条件的查询时,第二次查询结果中出现了第一次没有的“新行”,或者一些行消失了。
示例:
-- 事务 A
START TRANSACTION;
SELECT * FROM orders WHERE amount > 100; -- 返回 3 行
-- 此时事务 B 插入一条 amount=200 的记录并提交
SELECT * FROM orders WHERE amount > 100; -- 返回 4 行(幻读)
二、可重复读(Repeatable Read)能否解决幻读?
1. SQL 标准中的定义
- Repeatable Read:保证事务中对同一行的多次读取结果一致,避免不可重复读,但不保证避免幻读。
- Serializable:串行化,所有事务顺序执行,完全避免幻读。
2. MySQL InnoDB 的实现
MySQL 的 默认隔离级别是 Repeatable Read,并且 InnoDB 在 Repeatable Read 下可以避免幻读,原因是 使用了 Next-Key Lock(记录锁 + 间隙锁)。
为什么能避免幻读?
-
MVCC(快照读):
- 普通
SELECT
使用 一致性读,通过事务开始时的 Read View 提供稳定结果,不会看到新插入的数据。 - 所以在相同事务中,多次普通查询不会出现幻读。
- 普通
-
Next-Key Lock(当前读):
- 如果事务执行 加锁操作(SELECT … FOR UPDATE / UPDATE / DELETE),InnoDB 会对 索引记录 + 相邻间隙 加锁,防止其他事务插入新行。
- 这避免了幻读问题。
三、Repeatable Read 与幻读的关系
情况 1:普通查询(快照读)
START TRANSACTION;
SELECT * FROM orders WHERE amount > 100; -- 第一次查询
-- 事务 B 插入新行并提交
SELECT * FROM orders WHERE amount > 100; -- 第二次查询,仍然返回旧结果(MVCC)
结果:不会出现幻读,因为读取的是事务开始时的快照。
情况 2:加锁查询(当前读)
START TRANSACTION;
SELECT * FROM orders WHERE amount > 100 FOR UPDATE;
-- 事务 B 尝试插入一条 amount=200 的记录,会被阻塞,直到事务 A 提交
结果:不会出现幻读,因为 Next-Key Lock 锁定了间隙。
四、Next-Key Lock 机制详解
Next-Key Lock = 记录锁(Record Lock)+ 间隙锁(Gap Lock)
- 记录锁:锁定具体行,防止修改。
- 间隙锁:锁定两个记录之间的间隙,防止插入。
示意图(假设索引列值为 10、20、30):
锁定范围:
(10)---(20)---(30)
事务 A 锁定 (10,20) 间隙,其他事务无法在 10 和 20 之间插入新记录。
五、MySQL 四种隔离级别与幻读对比
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ UNCOMMITTED | ✅ | ✅ | ✅ |
READ COMMITTED | ❌ | ✅ | ✅ |
REPEATABLE READ | ❌ | ❌ | ❌(InnoDB 通过 Next-Key Lock 避免) |
SERIALIZABLE | ❌ | ❌ | ❌ |
注意:InnoDB 的 Repeatable Read 已经避免幻读,但在其他数据库(如 Oracle),Repeatable Read 不能避免幻读,必须用 Serializable。
六、为什么 InnoDB Repeatable Read 能避免幻读?
- 普通查询使用 MVCC 快照,不会读到其他事务插入的新行。
- 当前读(加锁语句)使用 Next-Key Lock,阻止其他事务在范围内插入新行。
七、面试高频问答
Q1:幻读和不可重复读区别?
- 不可重复读:同一行数据两次读取结果不同(因为更新)。
- 幻读:多次查询结果集行数不同(因为插入或删除)。
Q2:MySQL 默认隔离级别能避免幻读吗?
- 能,因为 InnoDB 在 Repeatable Read 下用 MVCC + Next-Key Lock 避免幻读。
Q3:Next-Key Lock 是什么?
- 记录锁 + 间隙锁,锁定行和相邻间隙,防止插入,避免幻读。
Q4:Serializable 和 Repeatable Read 区别?
- Serializable 通过 强制事务串行执行 避免所有并发问题,性能差。
- InnoDB 的 Repeatable Read 通过 MVCC + 锁 实现高性能,且避免幻读。
八、总结
- SQL 标准:Repeatable Read 不能完全避免幻读。
- MySQL InnoDB:Repeatable Read + MVCC + Next-Key Lock 可以避免幻读。
- 应用场景:默认隔离级别 Repeatable Read 足够安全,除非业务必须串行化。