最有效解法是将隔离级别降为read committed,因rc禁用gaplock,仅保留记录锁和插入意向锁,可显著缓解阻塞;但需确保binlog_format=row且业务能容忍幻读。

直接结论:MySQL 5.7 中 Gap Lock 导致的并发瓶颈,最有效、最可控的解法是将事务隔离级别从 RR 降级为 RC,但必须同步确认 binlog_format = ROW 且业务逻辑能容忍“同一事务内两次 SELECT 结果不同”。
为什么 RR 下 Gap Lock 会卡住高并发写入
在 RR 隔离级别下,InnoDB 默认使用 Next-Key Lock(记录锁 + 间隙锁),哪怕你只查 WHERE id = 100,只要 id 不是唯一索引,或者查询条件没走索引,就可能锁住整个索引区间。更常见的是范围更新:UPDATE t SET status = 2 WHERE created_at > '2026-05-01' —— 这会锁住所有匹配范围的间隙,导致后续 INSERT 或 UPDATE 同一范围的事务排队甚至死锁。
典型表现包括:
- 多个事务同时执行相同
SELECT ... FOR UPDATE(即使查不到数据)后,彼此阻塞 -
SHOW ENGINE INNODB STATUS中频繁出现lock_mode X locks gap before rec - 慢查询日志里没有慢 SQL,但应用层写入延迟突增、超时增多
降级到 RC 的实操步骤与关键检查项
RC 隔离级别下,InnoDB 默认只加 LOCK_REC_NOT_GAP(纯记录锁),不加间隙锁 —— 这是性能提升的核心来源。但以下四点必须手动验证或调整:
- 全局或会话级设置:
SET GLOBAL tx_isolation = 'READ-COMMITTED';或连接时指定tx_isolation=READ-COMMITTED - 确认 binlog_format 是
ROW:SHOW VARIABLES LIKE 'binlog_format';—— 若为MIXED或STATEMENT,RC 下某些 UPDATE 可能主从不一致 - 检查是否有唯一约束(
UNIQUE KEY/FOREIGN KEY):它们会在 RC 下仍触发间隙锁用于冲突检测,INSERT相同唯一值时照样阻塞 - 业务代码中避免“读-判-写”逻辑(如先
SELECT count(*)再INSERT):RC 下两次读可能看到不同结果,需改用INSERT ... ON DUPLICATE KEY UPDATE或加应用层幂等控制
RC 下仍可能被锁住?排查外键和唯一键隐式锁
别以为切到 RC 就万事大吉。只要表上有 UNIQUE KEY,两个事务同时执行:INSERT INTO user (email) VALUES ('a@b.com');,其中一个必然等待另一个提交 —— 这背后仍是间隙锁在起作用,用于防止唯一冲突。
如何确认是不是这个原因?
- 执行
SHOW ENGINE INNODB STATUS\G,在TRANSACTIONS部分找lock_mode X locks gap before rec并关联到duplicate key字样 - 临时去掉唯一索引测试(仅限预发/测试环境):
ALTER TABLE user DROP INDEX uk_email;,若阻塞消失,基本可定位 - 对高频插入场景,考虑用
REPLACE INTO或INSERT ... ON DUPLICATE KEY UPDATE替代先查后插
别碰 innodb_locks_unsafe_for_binlog
这个参数在 MySQL 5.7 中已被标记为 deprecated,配置文件里写了也不会生效,启动时还会报警告。它不是“关闭间隙锁”的开关,只是历史兼容残留。真正控制锁行为的是隔离级别本身 + 索引结构 + 查询是否命中索引。试图靠它绕过 Gap Lock,只会浪费调试时间。
最容易被忽略的一点:即使你设了 tx_isolation = READ-COMMITTED,如果某条 SQL 没走索引(EXPLAIN 显示 type = ALL),InnoDB 仍可能升级为全表扫描+行锁,此时并发提升有限。所以降级 RC 前,务必确保核心写入路径的 WHERE 条件都命中了合适索引。











