Skip to content

现在有个表,一列 id,一列 name,说一下幻读是怎么发生的?

约 684 字大约 2 分钟

MySQL字节

2025-03-20

⭐ 题目日期:

字节 - 2024/12/17

📝 题解:


幻读现象的产生(以表 users(id, name) 为例)

假设表结构如下:

CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50)
);

初始数据:

INSERT INTO users (id, name) VALUES (1, 'A'), (3, 'C'), (5, 'E');

幻读发生场景示例

事务 A事务 B 按以下步骤操作:

时间点事务 A事务 B
T1BEGIN;
T2SELECT * FROM users WHERE id > 3; → 结果:(5, 'E')
T3BEGIN;
T4INSERT INTO users (id, name) VALUES (6, 'F'); → 插入新记录 (6, 'F')
T5COMMIT;
T6SELECT * FROM users WHERE id > 3; → 结果:(5, 'E'), (6, 'F')(多出一条)
T7COMMIT;

结果:事务 A 在 T2 和 T6 的两次查询中,发现 id > 3 的记录从 1 条变为 2 条,幻读发生


幻读的核心原因

  1. 范围查询:事务 A 的查询条件是基于范围(id > 3)。
  2. 插入新记录:事务 B 插入了一条满足该范围的新记录(id=6)。
  3. 隔离级别:若隔离级别为 可重复读(Repeatable Read),但未正确使用锁机制时,仍可能发生幻读。

MySQL 如何解决幻读?

可重复读(默认隔离级别) 下,InnoDB 通过 Next-Key Locks 防止幻读:

  1. Next-Key Lock 范围
    • 当事务 A 执行 SELECT ... WHERE id > 3 时,锁定 (3, +∞) 的索引范围和间隙。
    • 事务 B 插入 id=6 时会被阻塞,直到事务 A 提交。
  2. 加锁后的操作流程
时间点事务 A事务 B
T1BEGIN;
T2SELECT * FROM users WHERE id > 3 FOR UPDATE; → 结果:(5, 'E')
T3BEGIN;
T4INSERT INTO users (id, name) VALUES (6, 'F');阻塞等待锁释放
T5COMMIT; → 释放锁
T6插入成功
T7COMMIT;

结果:事务 B 的插入操作被阻塞,事务 A 的两次查询结果一致,幻读被避免


幻读与不可重复读的区别

现象不可重复读幻读
数据变化类型同一记录的 值被修改范围内 新增或删除记录
关注点单行数据的一致性查询结果集的行数变化
解决机制MVCC(多版本快照)Next-Key Locks(锁定范围+间隙)

总结

  1. 幻读的触发条件
    • 隔离级别为 可重复读 时,未显式加锁的范围查询。
    • 其他事务插入或删除符合查询条件的数据。
  2. 解决方案
    • 使用 SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODE 显式加锁。
    • 依赖 InnoDB 的 Next-Key Locks 机制(默认启用)。
  3. 实际开发建议
    • 对需要严格一致性的范围查询显式加锁。
    • 理解隔离级别与锁机制的关系,避免高并发场景下的性能问题。