mysql幻读是怎么出现的_mysql并发事务分析

来源:这里教程网 时间:2026-02-28 20:49:45 作者:

幻读到底是什么现象?

幻读不是数据“变脏”或“被改”,而是同一个事务里,两次执行一模一样的范围查询(比如

SELECT * FROM orders WHERE status = 'pending'
),第二次却多出了一行——这行是别的事务在两次查询之间
INSERT
进来的。它像幽灵一样“凭空出现”,所以叫“幻读”。关键点:不是同一行被改了(那是不可重复读),而是**行数变了**。

为什么可重复读(RR)隔离级别下还会幻读?

MySQL 默认的

REPEATABLE READ
确实靠 MVCC 实现快照读,能挡住其他事务的
UPDATE/DELETE
对已存在行的影响,但对新插入的行无感——因为新行的
trx_id
比当前事务启动时的
snapshot
版本号还大,MVCC 不会把它纳入快照。所以:
• 普通
SELECT
(快照读):看不到新插入的行 → 表面没幻读
• 但一旦你执行
SELECT ... FOR UPDATE
INSERT ... SELECT
这类当前读,InnoDB 就必须查真实索引结构 → 新行就暴露了 → 幻读立刻发生

怎么真正拦住幻读?三个实操手段

1. 加 Next-Key Lock(最常用)
• 在范围条件上加锁,比如

SELECT * FROM t WHERE id > 100 FOR UPDATE
,InnoDB 不仅锁住已有记录,还会锁住
(100, +∞)
这个间隙
• 要求字段有索引(主键或二级索引),否则退化为表锁

2. 升级到
SERIALIZABLE

• MySQL 会自动把所有普通
SELECT
改成
SELECT ... LOCK IN SHARE MODE

• 并发急剧下降,线上慎用

3. 应用层配合(推荐高频场景)
• 比如“先查再插”逻辑,不用
SELECT ... INSERT
,改用
INSERT IGNORE
ON DUPLICATE KEY UPDATE

• 避免依赖两次查询的一致性,直接靠唯一约束兜底

最容易被忽略的坑

SELECT * FROM t WHERE name = 'xxx'
—— 如果
name
没索引,InnoDB 无法加间隙锁,幻读照常发生
UPDATE
语句只锁匹配到的行,不锁间隙;如果想防幻读,得显式加
FOR UPDATE
或改写为带范围条件的锁读
READ COMMITTED
下连 MVCC 快照都每条语句刷新一次,幻读比 RR 更频繁,别误以为“读已提交更安全”

相关推荐