根本原因是repeatable read默认启用间隙锁和临键锁,导致锁范围大、持有时间长;而read committed仅在唯一约束检查时加间隙锁,锁粒度更小、冲突更少。

为什么REPEATABLE READ隔离级别更容易触发Lock wait timeout
MySQL默认用REPEATABLE READ,但它在高并发更新场景下比READ COMMITTED更易锁等待超时。根本原因不是“它更严格”,而是它默认启用间隙锁(gap lock)和临键锁(next-key lock)。比如执行DELETE FROM users WHERE status = 1且status无索引时,InnoDB可能对整个索引范围加锁,把本不该被影响的行也堵住。
而READ COMMITTED下,间隙锁仅在唯一约束冲突检查时生效,普通DML不加间隙锁,锁粒度更小、持有时间更短。
- RR模式下,
UPDATE t SET x=1 WHERE id > 100 AND id 会锁住(100,200)这个区间,哪怕当前没数据 - RC模式下,同一条语句只锁实际命中的行,不锁间隙
- RR模式下长事务+范围条件+缺失索引,极易形成“锁住一片”的局面
如何安全地临时切到READ COMMITTED
改隔离级别不能只靠SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED——这只能影响后续语句,对已开启但未提交的事务无效。真正起效的前提是:事务尚未开始,或上一个事务已COMMIT/ROLLBACK。
Java应用中常见错误是:在Spring的@Transactional方法里动态改隔离级别,结果被AOP代理忽略,还是走默认RR。
- 推荐方式:在连接池初始化时统一设置
sessionVariables=transaction_isolation=READ-COMMITTED(如HikariCP的connection-init-sql) - 避免在事务中调用
set transaction isolation level,MySQL会报Warning且不生效 - 切换后需验证:执行
SELECT @@tx_isolation确认返回READ-COMMITTED - 注意副作用:RC下可能出现不可重复读,业务逻辑若依赖“同一事务内多次SELECT结果一致”,就得重审
查锁不能只看INNODB_TRX,必须连查INNODB_LOCK_WAITS
SELECT * FROM information_schema.INNODB_TRX只告诉你“谁在跑”,但看不出谁在等谁。真正定位阻塞源,得靠三表关联:
SELECT r.trx_id waiting_trx_id,
r.trx_mysql_thread_id waiting_thread,
r.trx_query waiting_query,
b.trx_id blocking_trx_id,
b.trx_mysql_thread_id blocking_thread,
b.trx_query blocking_query
FROM information_schema.INNODB_LOCK_WAITS w
JOIN information_schema.INNODB_TRX b ON b.trx_id = w.blocking_trx_id
JOIN information_schema.INNODB_TRX r ON r.trx_id = w.requesting_trx_id;
这个查询结果里,blocking_query字段常为空——因为阻塞者可能只是持有一个未提交的UPDATE,之后卡在应用层(比如远程HTTP调用没返回),SQL早就执行完了,但事务还开着。
- 如果
blocking_query为空,重点看trx_started和trx_state:长时间RUNNING且trx_wait_started为NULL,大概率是应用没提交 -
trx_operation_state显示fetching rows或updating or deleting rows,说明真正在执行慢SQL,不是应用卡住 - 别直接
KILL线程,先SHOW FULL PROCESSLIST确认Command列是不是Sleep且Time远超正常值
innodb_lock_wait_timeout设小了反而更难排查
有人把innodb_lock_wait_timeout从50秒改成5秒,以为能“快速失败”。结果是:错误出现更频繁,但你永远看不到谁在阻塞——因为等不到你登录上去查,它已经自己回滚了。
这个参数本质是“给DBA留出响应窗口”,不是给应用兜底的。设太小,等于主动放弃可观测性。
- 生产环境建议保持默认50秒,或最多调到60–90秒
- 真正该压的是应用侧事务耗时:比如把
UPDATE后接HTTP调用的代码拆成两步,先提交再调用 - 监控要盯
information_schema.INNODB_TRX里trx_started时间超过30秒的事务,而不是等报错才动 - 自动重试逻辑必须带退避(如指数级延迟),否则5秒超时+立即重试=雪崩式锁竞争











