repeatable read默认启用间隙锁和临键锁,锁住索引区间而非单行,导致update/delete等操作锁范围扩大;read committed关闭间隙锁,仅锁命中行,但需全局设置隔离级别且binlog_format为row。

REPEATABLE READ 隔离级别本身不“增加”锁冲突,但它默认启用间隙锁(Gap Lock)和临键锁(Next-Key Lock),而这些锁的覆盖范围比 READ COMMITTED 大得多——这才是锁冲突变多的真正原因。
REPEATABLE READ 下间隙锁会锁住整个索引区间
普通 SELECT 不加锁(快照读),但一旦执行 UPDATE、DELETE 或 SELECT ... FOR UPDATE,InnoDB 就会基于索引结构加锁。关键点在于:
- 如果查询条件走索引(主键、唯一索引、二级索引均可),且是范围查询(如
WHERE status > 10、WHERE name BETWEEN 'a' AND 'z'),InnoDB 会加 Next-Key Lock:行锁 + 左侧间隙锁 → 锁住“值区间”,不只是已有记录 - 即使只查一条记录,比如
WHERE id = 100,若id是主键,InnoDB 加的是 行锁;但若status是普通索引且重复值多,InnoDB 可能退化为锁整段索引页,甚至锁住大量无关间隙
常见错误现象:
-
UPDATE t SET x=1 WHERE status = 'pending',而status只有 2~3 个枚举值 → 实际可能锁住整个status索引分支,阻塞其他事务插入/更新任意status值 -
SELECT * FROM t WHERE created_at > '2024-01-01' FOR UPDATE,但created_at没索引 → 全表扫描 → 退化为表级意向锁 + 每行行锁,冲突概率飙升
READ COMMITTED 关闭间隙锁,锁粒度收缩到命中的行
READ COMMITTED 模式下,InnoDB 不使用间隙锁(除极少数唯一索引等值查询场景外),只对实际读取/修改的行加记录锁(Record Lock)。这意味着:
- 插入操作只要不与已锁定的行冲突,就能并发进行
- 同一范围内多次
SELECT ... FOR UPDATE不会相互阻塞(除非命中同一行) - 锁等待范围大幅缩小,
innodb_lock_wait_timeout触发概率显著下降
但必须满足两个前提:
-
binlog_format必须为ROW(否则主从数据不一致) - 隔离级别需全局设置:
SET GLOBAL tx_isolation = 'READ-COMMITTED',仅会话级设置无效
索引缺失或失效时,REPEATABLE READ 锁行为更危险
没有索引支撑的当前读,InnoDB 无法精准定位间隙,只能退化为:
- 全表扫描 → 对所有行加记录锁,并持有表级意向锁(
IX) - 无法加间隙锁 → 幻读风险上升,同时锁竞争加剧(因为所有写操作都要等全表锁释放)
-
EXPLAIN显示type: ALL或key: NULL时,FOR UPDATE实际等效于“准表锁”
容易踩的坑:
- 在低基数字段(如
is_deleted TINYINT)上建索引,却在查询中写WHERE is_deleted = 0→ MySQL 可能放弃索引,触发全表扫描加锁 - 使用函数索引但查询没匹配定义,例如索引是
INDEX (UPPER(name)),却查WHERE name = 'abc'→ 索引失效,间隙锁失效,锁范围失控
锁冲突不是隔离级别“设计出来”的问题,而是它为防幻读所依赖的间隙锁机制,在索引设计不合理、查询写法不严谨、字段基数过低等现实条件下暴露出来的副作用。真正要调优,得从 EXPLAIN 看执行计划开始,而不是直接改隔离级别。











