read committed是唯一能实质性关闭间隙锁的隔离级别,需全局设置、配合row格式binlog,并接受不可重复读;唯一索引冲突和外键引用时仍会触发间隙锁。

READ COMMITTED 是唯一能实质性关闭 Gap Lock 的隔离级别
MySQL 没有 gap_lock=OFF 这类开关,Gap Lock 的存在由事务启动时的全局隔离级别决定。只有 READ COMMITTED 能让 InnoDB 在绝大多数 INSERT/UPDATE 场景下跳过 Gap Lock,只对实际命中的行加 Record Lock。这不是“禁用功能”,而是该隔离语义本身不依赖 Gap Lock 防幻读——所以自然不加。
关键点在于:它必须是全局设置,会话级 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED 无效。必须执行:
-
SET GLOBAL tx_isolation = 'READ-COMMITTED'(注意连字符写法) - 重启所有已存在的客户端连接(旧连接仍按原隔离级别运行)
- 确认生效:
SELECT @@global.tx_isolation返回READ-COMMITTED
binlog_format 必须同步设为 ROW,否则主从必然不一致
漏掉这步,上线后可能突然出现主从延迟飙升、数据错位,且日志里查不到明显报错。原因很直接:READ COMMITTED 下同一条 SQL 在不同事务中可能看到不同快照(不可重复读),而 STATEMENT 格式只记录 SQL 文本,从库重放时无法还原这种非确定性行为。
检查与修改方式:
- 查当前格式:
SHOW VARIABLES LIKE 'binlog_format' - 临时改(重启后失效):
SET GLOBAL binlog_format = 'ROW' - 持久化:在
my.cnf中写入binlog_format = ROW并重启 MySQL
Gap Lock 在唯一索引冲突和外键引用时仍会触发
即使启用了 READ COMMITTED,以下两种情况仍会加 Gap Lock:
- 插入时违反唯一索引约束(如重复
email),InnoDB 需锁定冲突值所在间隙以保证约束检查原子性 - 涉及外键约束的 DML 操作(如父表某记录被删前,子表对应外键字段的插入范围会被隐式加 Gap Lock)
这意味着:你不能指望 Gap Lock “完全消失”,只能消除它在普通业务插入路径上的干扰。如果表有高频唯一冲突或强外键依赖,仍需通过应用层重试或预检规避。
别忽略 LOCK_DATA 显示的间隙边界不是你 INSERT 的值
当 INSERT 卡住时,查 performance_schema.data_locks 看到的 LOCK_DATA 字段(如 (5,10))是相邻记录构成的开区间端点,不是你正在插的 ID 值。比如你插 id=7 卡住,实际是因为别人锁了 (5,10) 这个间隙——你根本没动那两行,但位置落在了锁范围内。
真正麻烦的不是 Gap Lock 存在,而是它藏得深、边界模糊。排查时务必结合 INNODB_TRX 找等待事务,再关联 data_locks 看 LOCK_MODE 和 LOCK_DATA,否则容易误判为死锁或索引缺失。











