MySQL通过MVCC与Next-Key Lock分工协作解决幻读:快照读依赖MVCC生成一致性视图实现非阻塞可见性,当前读则依靠Next-Key Lock(记录锁+间隙锁)锁定索引区间防止插入。

MySQL 在高并发下解决幻读,靠的不是单一机制,而是 MVCC 和 Next-Key Lock 的分工协作:快照读走 MVCC,当前读走 Next-Key Lock。
快照读(普通 SELECT)为什么不会幻读?
MVCC 不是“阻止插入”,而是让事务只看到自己该看的数据版本。事务启动时生成一致性视图(read view),后续所有 SELECT 都基于这个视图读取——即使其他事务插入并提交了新行,只要其 trx_id 大于当前事务的 view.low_limit_id,就不会被读到。
- 关键点:
SELECT不加锁,不阻塞别人插入,但也不“看见”这些插入 - 依赖前提:事务隔离级别必须是
REPEATABLE READ(InnoDB 默认),否则在READ COMMITTED下每次SELECT都会新建 read view,可能看到新提交的行 - 陷阱:如果查询条件没走索引,InnoDB 无法精准判断哪些行属于“该版本可见”,可能退化为全表扫描+逐行判断,性能差且易受干扰
当前读(SELECT ... FOR UPDATE / LOCK IN SHARE MODE)怎么锁住“间隙”?
当你显式加锁或执行 UPDATE/DELETE 时,InnoDB 必须保证范围查询结果稳定,这时就启用 Next-Key Lock:它 = 记录锁(Record Lock) + 间隙锁(Gap Lock)。
- 例如执行
SELECT * FROM t WHERE id > 10 FOR UPDATE,假设现有索引值为5, 10, 15, 20,则锁定区间为(10, 15]和(15, 20],以及(20, +∞)—— 这些空隙里不允许插入新id - 若
WHERE条件无索引,InnoDB 会升级为表级间隙锁(即锁整个索引范围),并发能力断崖下跌,务必避免 - 注意:
INSERT本身会先获取插入意向锁(Insert Intention Lock),它和间隙锁冲突,所以插入会被阻塞,直到当前读事务结束
为什么 REPEATABLE READ 级别下仍有“幻读”报错?
这不是 MVCC 或锁失效,而是你混淆了“不可见”和“不允许插入”。典型场景是唯一约束冲突:
解析 MySQL 8.0 的 DDL 脚本,并将表结构导出为 Excel 格式。适用于用户需将 MySQL 的 `CREATE TABLE` 语句转换为结构化的 Excel 文件场景,导出内容包含字段名、数据类型、长度及精度等信息。支持多张表的批量处理,每张表对应 Excel 中的一个独立工作表。适用于数据库文档编写、数据字典生成及模式分析等任务。
- 事务 A 执行
SELECT * FROM user WHERE name = 'alice'(快照读),没查到,准备插入 - 事务 B 插入
'alice'并提交 - 事务 A 执行
INSERT INTO user(name) VALUES('alice')→ 报Duplicate entry 'alice' for key 'uk_name' - 表面像幻读,实则是事务 A 在插入时触发了唯一索引检查,而该索引项已在事务 B 中落盘
- 解法:把
SELECT改成SELECT ... FOR UPDATE,提前用 Next-Key Lock 锁住name = 'alice'对应的间隙,让事务 B 的插入被阻塞
高并发写场景下最容易被忽略的点
Next-Key Lock 的生效前提是查询必须走索引,且索引要覆盖查询条件和更新目标。没有索引,或者用了 OR、函数、隐式类型转换导致索引失效,InnoDB 就只能锁表或锁全索引树,吞吐量直接归零。
另外,SERIALIZABLE 看似一劳永逸,但它会让所有 SELECT 自动转成 LOCK IN SHARE MODE,读写互斥加剧,线上极少使用。真正稳的方案是:索引设计到位 + 当前读明确加锁 + 应用层重试逻辑兜底。










