mysql 5.7默认隔离级别是repeatable read,它通过mvcc保证快照读下无幻读,但当前读(如select ... for update)仍可能发生幻读;serializable虽理论上杜绝幻读,却因隐式加锁导致qps骤降、死锁风险上升,且无法根治无索引场景下的间隙插入问题。

REPEATABLE READ 是 MySQL 5.7 默认隔离级别,但它不能完全消除幻读——尤其在当前读(如 SELECT ... FOR UPDATE)场景下。单纯靠改隔离级别“修复”幻读,容易误判问题根源。
为什么 SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE 不推荐
在 MySQL 5.7 中执行 SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE 后,所有普通 SELECT 会自动转为加锁读(等价于隐式 SELECT ... LOCK IN SHARE MODE),导致:
- QPS 显著下降,尤其在高并发只读场景下抖动明显
- 原本无锁的简单查询也会被阻塞,容易引发连接池耗尽
- 无法规避间隙插入——
SERIALIZABLE对范围查询加的是 next-key lock,但若查询条件未命中索引,仍可能退化为表锁
READ COMMITTED 级别下幻读更频繁,且无法靠 MVCC 拦截
MySQL 5.7 的 READ COMMITTED 级别不使用间隙锁(gap lock),每次快照读都基于语句启动时的最新已提交版本。这意味着:
- 两次
SELECT ... WHERE x = ?可能返回不同行数,因为其他事务在中间插入了新行 -
MVCC只保证单行可见性,不维护范围一致性;没有 gap lock,就无法阻止“符合 where 条件的新行插入” - 即使加了
FOR UPDATE,也只锁住当前查到的行,不锁住“该值尚未存在的空隙”
真正可控的修复方式:用 next-key lock 覆盖查询范围
幻读是否发生,取决于你用的是快照读还是当前读,以及锁是否覆盖了可能插入的间隙。在 REPEATABLE READ 下,唯一可靠的方式是让查询触发 next-key lock:
- 确保查询条件走**有效索引**(主键或二级索引),否则会升级为表级锁或锁失效
- 避免全表扫描:例如
SELECT * FROM t WHERE status = 1 FOR UPDATE,若status无索引,则锁不住间隙,B 事务仍可插入 - 对范围查询显式加锁:比如想防止
age BETWEEN 20 AND 30区间插入,应写成SELECT * FROM t WHERE age BETWEEN 20 AND 30 FOR UPDATE,InnoDB 会加 (20,30] 的 next-key lock - 等值查询若命中唯一索引,会退化为行锁;如需锁住间隙,改用范围条件,例如把
WHERE id = 5改为WHERE id >= 5 AND id
配置文件里设 transaction-isolation 不等于运行时安全
在 my.cnf 中写 transaction-isolation = REPEATABLE-READ 只影响新连接,默认值而已。它不解决已有逻辑缺陷:
- 应用层没显式开启事务,或事务内混用快照读与当前读,幻读照样发生
- ORM 框架(如 MyBatis、Hibernate)可能自动包装
SELECT,但未必带FOR UPDATE,需检查生成 SQL - 执行
SELECT @@tx_isolation只能看到会话当前值,无法反推业务是否真按预期加锁
关键点不是“调哪个隔离级别”,而是“哪条语句在什么条件下实际持有了什么锁”。REPEATABLE READ 提供了 next-key lock 的能力,但不会自动启用——得靠你写的 SQL 触发。











