rc级别下update不加间隙锁是明确设计选择而非意外优化,因innodb主动放弃防幻读语义,仅对实际匹配行加记录锁;需binlog_format=row且where走索引才生效,否则仍可能全表加锁。

RC级别下UPDATE不加间隙锁,是设计选择不是意外优化
READ COMMITTED 级别下 UPDATE 只锁实际匹配的行,根本原因在于 InnoDB 主动禁用了间隙锁(Gap Lock)——这不是“没能力锁”,而是明确放弃防幻读语义后,把锁范围收缩到最小必要单元。RR 级别靠 Next-Key Lock(记录锁 + 间隙锁)堵住幻读,RC 则用“半一致性读”机制让 UPDATE 更克制:扫描到被其他事务 X 锁住的行时,先读已提交版本判断 WHERE 是否满足,只对真正要改的行加记录锁。
没命中记录时,RC 真的完全不锁?
是的,只要满足两个前提:binlog_format = ROW 且 WHERE 条件走索引。比如执行 UPDATE t SET v = 1 WHERE id = 999,而表中根本没有 id = 999 的记录:
- 在 RR 下,会加 Gap Lock 锁住 (prev_id, next_id) 这个间隙,阻止插入
- 在 RC 下,InnoDB 扫描索引后发现无匹配,不加任何锁,INSERT 同样条件的行立刻成功
- 但若
WHERE没走索引(如字段类型不匹配、函数包裹、隐式转换),仍会全表扫描并为每行加记录锁——此时“没命中”只是逻辑结果,物理上已锁了所有行
为什么你设了 RC 却还是看到间隙锁?
常见失效场景不是隔离级别本身的问题,而是环境或配置没对齐:
-
SHOW VARIABLES LIKE 'binlog_format'返回STATEMENT→ MySQL 可能悄悄回退到 RR 行为,必须是ROW - ORM 或连接池(如 HikariCP)在获取连接后执行
SET SESSION tx_isolation = REPEATABLE-READ,把你之前设的覆盖掉 -
SELECT @@transaction_isolation返回值不是READ-COMMITTED(注意连字符和大小写),说明设置未生效 - 使用了
SELECT ... FOR UPDATE或SELECT ... LOCK IN SHARE MODE—— 这些语句在 RC 下依然可能触发间隙锁,和 UPDATE 行为不同
RC 下 UPDATE 锁行为的边界在哪?
真正决定锁范围的从来不是隔离级别开关,而是执行计划是否精准定位:
- 主键等值更新(
WHERE id = ?)→ 只锁那一行,RC 和 RR 表现一致 - 非唯一索引等值更新(
WHERE status = 'pending')→ RC 只锁匹配的行;RR 加 Next-Key Lock,锁住该 status 值对应的所有索引间隙 -
WHERE a = 1 OR b = 2→ 优化器可能放弃索引走全表扫描,RC 下也锁全部扫描行,不是“轻量”而是“伪轻量” - 联合索引
INDEX(a, b),用WHERE b = 5→ 不满足最左前缀,索引失效,RC 下照样全表加记录锁
别迷信 RC 能自动解耦,它只是移除了间隙锁这层“额外负担”,但行锁本身不会消失——没索引,就等于没准星,再好的枪也打不准。











