Next-Key Lock在REPEATABLE READ下同时锁定记录和间隙以防止幻读,导致大事务锁住大片范围;READ COMMITTED仅加行锁、减少锁范围但引发不可重复读;优化需索引覆盖、分批提交、移出外部操作。

为什么大事务在REPEATABLE READ下会锁住大片范围
不是事务本身“大”有问题,而是REPEATABLE READ隔离级别在InnoDB中默认启用Next-Key Lock:它同时加Record Lock(锁行)和Gap Lock(锁间隙),目的是防止幻读。但一个执行2秒的UPDATE WHERE status = 'pending',可能扫描并锁定几百行+它们之间的所有间隙——这些锁直到COMMIT才释放。并发事务一碰就卡,死锁概率指数上升。
改用READ COMMITTED能直接减少锁范围吗
能,但有代价:
-
READ COMMITTED下,InnoDB只加Record Lock,不加Gap Lock,锁粒度变小、持有时间缩短 - 副作用是不再保证可重复读,会出现不可重复读(同一事务内两次
SELECT结果不同) - 它不解决锁序问题:如果两个事务以不同顺序更新同一组行,仍可能死锁
- 对已存在的业务逻辑(比如依赖RR语义做幂等校验)可能造成行为变更,需全链路验证
不改隔离级别,怎么让大事务少锁
核心思路是:缩小扫描范围 + 缩短锁持有时间 + 消除不确定性
- 确保
WHERE条件走索引,避免全表扫描触发全表Next-Key Lock;用EXPLAIN确认key字段非NULL - 分批操作时,显式控制事务边界:每批
DELETE/UPDATE后必须COMMIT,别依赖连接池自动提交;查SELECT @@autocommit确认是否为1 - 带
LIMIT的范围操作必须加ORDER BY单调字段(如id),否则InnoDB不保证扫描顺序,会导致漏删或重复锁 - 把耗时外部操作(如HTTP调用、文件写入)移出事务,避免锁长时间空等
- 调整
innodb_lock_wait_timeout到10~30秒,让等待线程快速失败而非堆积
最容易被忽略的锁陷阱
很多人以为“只要加了LIMIT就安全”,却忘了SELECT ... FOR UPDATE或UPDATE在REPEATABLE READ下依然会基于查询条件推导出整个扫描区间并上Next-Key Lock。哪怕只更新1行,若条件没走索引,或索引选择性差(如status只有几个值),实际锁住的可能是成千上万条记录之间的全部间隙。这种锁不是“按行计数”的,是“按区间覆盖”的——这才是大事务里最隐蔽的锁爆炸点。


















