mysql的repeatable read无法防止幻读,因其mvcc快照不锁定间隙,普通select不加锁;只有serializable级别或显式加锁(如for update)才能防幻读。

MySQL 的 REPEATABLE READ 默认隔离级别**并不能完全防止幻读**,仅靠修改 tx_isolation 变量(或其别名 transaction_isolation)设为 REPEATABLE READ 是不够的。
为什么 set session transaction isolation level repeatable read 无法防幻读
幻读本质是其他事务在当前事务执行期间插入了满足查询条件的新行,而 REPEATABLE READ 下的 MVCC 快照只覆盖“已存在”的行——它不锁定间隙(gap),也不阻止新记录插入。InnoDB 在该级别下对普通 SELECT 使用一致性非锁定读,不加任何锁,自然拦不住 INSERT。
-
SELECT * FROM t1 WHERE id > 3;在事务 A 中执行两次,事务 B 在中间INSERT INTO t1 VALUES (5); COMMIT;→ 第二次查询多出一行,即幻读 -
REPEATABLE READ能保证“已读过的行值不变”,但不承诺“结果集行数不变” - 仅靠
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ不会自动启用间隙锁或 next-key 锁,除非你显式用了SELECT ... FOR UPDATE或SELECT ... LOCK IN SHARE MODE
真正能防幻读的设置只有 SERIALIZABLE
只有将隔离级别设为 SERIALIZABLE,MySQL 才会强制把所有普通 SELECT 当作 SELECT ... LOCK IN SHARE MODE 处理,隐式加锁(包括间隙锁),从而阻塞并发插入。
- 设置会话级:
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; - 设置全局级:
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;(需 SUPER 权限,且新连接才生效) - 注意:该级别下,
SELECT会阻塞INSERT、UPDATE、DELETE,反过来也成立,高并发写场景吞吐骤降 - 验证是否生效:
SELECT @@transaction_isolation;(注意:5.7.20+ 推荐用此变量名,tx_isolation已弃用)
想兼顾性能又防幻读?别只改 tx_isolation,得加锁
多数业务不需要全局 SERIALIZABLE,而是对关键查询主动加锁。此时隔离级别保持 REPEATABLE READ 即可,重点在 SQL 写法:
- 用
SELECT ... FOR UPDATE:在查询范围内加 next-key 锁(行锁 + 间隙锁),阻止其他事务插入“幻行” - 用
SELECT ... LOCK IN SHARE MODE:加共享 next-key 锁,允许并发读,但阻塞写和插入 - 确保 WHERE 条件能命中索引(最好是唯一/主键),否则会退化为表级锁,放大阻塞范围
- 避免在
REPEATABLE READ下依赖“两次 SELECT 结果一致”来判断数据是否被插入——这是幻读温床
真正防幻读不是调个变量就完事;tx_isolation 只是开关,背后起作用的是锁策略和 MVCC 行为。没加锁的 SELECT,再高的隔离级别也挡不住插入。











