MySQL幻读是怎么出现的

MySQL 幻读详解

幻读(Phantom Read)是数据库并发事务中的一种现象,理解它需要先区分几个相关概念。

一、什么是幻读

幻读 :在一个事务内,两次执行相同的范围查询 ,第二次查询却看到了第一次查询没有看到的新插入的行(像出现了"幻觉")。

注意区分三个概念:

现象 定义 关注点
脏读 读到其他事务未提交的数据 未提交数据
不可重复读 两次读同一行,值变了 同一行的修改
幻读 两次范围查询,结果集行数变了 结果集的新增/删除

关键区别:不可重复读针对单行值的更新,幻读针对结果集行数的增减。

二、幻读是怎么出现的

场景演示

假设表 t 有字段 id(主键)、c(普通索引)、d,隔离级别为 可重复读(RR),但先看不加锁的情况:

text

复制代码
时刻   事务A                          事务B
T1     BEGIN;
T2     SELECT * FROM t WHERE id > 2;  -- 返回 1 行(id=3)
T3                                     BEGIN;
T4                                     INSERT INTO t VALUES(5,...);
T5                                     COMMIT;
T6     SELECT * FROM t WHERE id > 2;  -- 若返回 2 行(id=3,5) → 幻读

在 RR 隔离级别下,MySQL 通过 MVCC 快照读 让 T6 仍然只看到 1 行,从而避免了大部分幻读。但幻读在以下情况仍会出现:

1. 当前读(加锁读)导致的幻读

sql

复制代码
-- 事务A
BEGIN;
SELECT * FROM t WHERE id > 2 FOR UPDATE;  -- 当前读,加锁
-- 此时锁住了 id>2 的范围

-- 事务B
INSERT INTO t VALUES(5,...);  -- 如果没被间隙锁挡住,就能插入

-- 事务A 再查
SELECT * FROM t WHERE id > 2 FOR UPDATE;  -- 多出一行 → 幻读

当前读FOR UPDATELOCK IN SHARE MODEUPDATEDELETE)读取的是最新数据,不走 MVCC 快照,所以可能看到新插入的行。

2. 快照读与当前读混用导致的幻读

这是最经典的"幻读"案例:

text

复制代码
时刻   事务A                                    事务B
T1     BEGIN;
T2     SELECT * FROM t WHERE id > 2;            -- 快照读,返回 id=3
T3                                              BEGIN;
T4                                              INSERT id=5; COMMIT;
T5     UPDATE t SET d=100 WHERE id > 2;         -- 当前读!会更新 id=3 和 id=5
T6     SELECT * FROM t WHERE id > 2;            -- 返回 id=3,5 → 幻读

原因 :T5 的 UPDATE 是当前读,它更新了事务B新插入的 id=5 这行。而 T6 的快照读虽然用的是 T1 时刻的快照,但 UPDATE 相当于把这行"变成"了自己事务修改过的行,后续快照读就能看到它了(这行对本事务可见)。

这是 MySQL RR 级别下唯一无法完全避免的幻读场景。

三、InnoDB 如何解决幻读

1. MVCC(多版本并发控制)------ 解决快照读幻读

  • 每行记录有隐藏字段 DB_TRX_ID(最近修改事务ID)、DB_ROLL_PTR(回滚指针)

  • 事务开始时生成 ReadView,记录当前活跃事务列表

  • 快照读时,只读取"对本事务可见"的版本

  • 因此事务A两次普通 SELECT 看到的是同一个快照,不会幻读

2. 间隙锁(Gap Lock)+ Next-Key Lock ------ 解决当前读幻读

  • Record Lock:锁单行记录

  • Gap Lock:锁一个范围区间(开区间),防止其他事务在间隙中插入

  • Next-Key Lock = Record Lock + Gap Lock,锁住记录及其前面的间隙

例如 SELECT * FROM t WHERE id > 2 FOR UPDATE,InnoDB 会对 (2, +∞) 加 Next-Key Lock,其他事务想插入 id=5 会被阻塞,从而防止当前读幻读。

注意:在 RC(读已提交) 隔离级别下,间隙锁基本失效(只锁行不锁间隙),所以 RC 无法防幻读。

四、总结

隔离级别 快照读幻读 当前读幻读
读未提交 RU
读已提交 RC
可重复读 RR 不会(MVCC) 不会(间隙锁)*
串行化 Serializable 不会 不会

* 例外:快照读 + 当前读混用时,RR 仍可能出现幻读,因为 UPDATE 会把新插入行纳入本事务可见范围。

核心要点

  1. 幻读 = 范围查询结果集行数变化

  2. 快照读靠 MVCC 防幻读,当前读靠 间隙锁 防幻读

  3. RR 下混用快照读和当前读,是幻读的唯一漏网之鱼

  4. 很多公司用 RC + 行锁,此时需在应用层处理幻读问题

相关推荐
百万蹄蹄向前冲1 小时前
密码都对Node.js却连不上Linux数据库
linux·mysql
浅念-1 小时前
Redis基础详解:单线程模型、String与Hash数据结构
服务器·数据库·redis·sql·mysql·nosql数据库·nosql
updayday8541 小时前
离职域账号状态变更与Ping64操作记录核对
大数据·网络·数据库·安全·智能路由器
aramae1 小时前
MySQL内置函数(7)
开发语言·笔记·后端·mysql·其他
Nturmoils2 小时前
只恢复一张表,别把整个库都还回去
数据库
龙腾AI白云2 小时前
大语言模型:从语言理解到通用智能的跃迁
数据库·人工智能·机器学习·知识图谱
SelectDB3 小时前
快手基于 Apache Doris 千亿多模态检索的实践
大数据·数据库·数据分析
SelectDB3 小时前
StarRocks 迁移至 Apache Doris 完整指南:三步完成结构、数据与业务平滑切换
大数据·数据库·数据分析
这个DBA有点耶3 小时前
InnoDB索引组织表下,复合主键和自增主键的物理存储差异与选型对比
数据库·mysql·架构