read committed 能绕开间隙锁,因其禁用间隙锁(唯一约束等例外),仅对命中行加记录锁,且采用语句级mvcc快照,降低锁粒度与版本留存压力。

READ COMMITTED 为什么能绕开间隙锁
MySQL 在 REPEATABLE READ 下默认启用间隙锁(Gap Lock)和临键锁(Next-Key Lock),哪怕你只查主键,只要 WHERE 条件涉及索引范围(比如 WHERE status = 'pending' 或 WHERE id > 100),InnoDB 就可能锁住整段索引间隙。而 READ COMMITTED 会禁用间隙锁(唯一约束和外键检查除外),只对实际命中的行加记录锁(Record Lock)。
这意味着:
- INSERT 不再被无故阻塞:比如事务 A 执行
SELECT * FROM orders WHERE user_id = 123 FOR UPDATE,在 RR 下会锁住user_id = 123对应的间隙;在 RC 下,只要没命中行,就不锁间隙,事务 B 可立刻插入新订单 - UPDATE/DELETE 锁粒度更小、持有时间更短:RC 下锁只持续到语句结束(非事务结束),RR 下要等到 COMMIT 才释放
- 死锁概率显著下降:尤其在
INSERT ... SELECT或批量状态更新场景中,锁冲突路径大幅收窄
MVCC 快照生命周期缩短直接减少资源争用
REPEATABLE READ 使用事务级 MVCC 快照:事务启动时生成一次,整个事务期间复用。这要求 InnoDB 持有所有旧版本直到该事务结束,容易拖慢 purge 线程、撑高 innodb_history_list_length,进而影响全局性能。
READ COMMITTED 改为语句级快照:每次 SELECT 都基于最新已提交版本重新生成快照。虽然多了点版本过滤开销,但换来的是:
- 旧版本数据可被更快清理,减少 undo log 占用和 purge 压力
- 长事务不再绑架整个实例的版本链管理
- 高并发下内存与 CPU 开销更平稳,不会因个别慢事务导致雪崩式延迟
为什么不是所有场景都适用 READ COMMITTED
降级隔离级别不是“开箱即用”的性能开关,它改变的是读一致性语义本身。以下情况必须提前验证:
- 应用是否隐式依赖“可重复读”:比如先
SELECT COUNT(*)判断是否存在,再INSERT,且未建唯一索引——RC 下可能插入重复数据 - ORM 是否硬编码了隔离级别:Spring 的
@Transactional(isolation = Isolation.REPEATABLE_READ)会覆盖数据库配置 - 范围查询逻辑是否假设结果集稳定:如某段代码认为 “两次
SELECT * FROM t WHERE c > X行数不变”,RC 下必然失效 - Binlog 格式是否为
ROW:RC 是 ROW 格式的强前提,否则主从不一致风险上升
连接池初始化阶段必须显式设置才生效
很多人改完 my.cnf 中的 transaction-isolation = READ-COMMITTED 就以为万事大吉,但老连接仍维持原会话级别,ORM 也可能在获取连接后立即开启事务,导致 SET SESSION 失败。
真正落地必须双保险:
- 在连接池配置里加
initSql = "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED" - 避免在事务中动态修改:一旦执行过
START TRANSACTION,再执行SET SESSION TRANSACTION ISOLATION LEVEL ...会报错Can’t change transaction isolation level when a transaction is active - 确认
SELECT @@session.transaction_isolation返回值,别只看@@global.transaction_isolation
最易被忽略的一点:间隙锁的关闭不是绝对的——唯一索引等值查询失败时(比如查不到记录),InnoDB 仍会加间隙锁以保证唯一性,这点在 RC 和 RR 下行为一致。











