innodb_lock_wait_timeout超时仅中断当前语句,不回滚事务;默认只回滚被阻塞的那条sql,事务仍活跃,需应用层捕获错误后主动rollback,否则可能导致数据不一致。

innodb_lock_wait_timeout 超时后只中断语句,不回滚事务
MySQL 默认行为是:当 innodb_lock_wait_timeout 触发时,仅终止当前被阻塞的那条 SQL(比如 UPDATE 或 SELECT ... FOR UPDATE),并抛出错误 Lock wait timeout exceeded; try restarting transaction。但事务本身仍处于活跃状态,之前已执行的 INSERT、UPDATE、DELETE 都未提交也未回滚。
这意味着如果应用没捕获错误并手动 ROLLBACK,后续继续执行其他语句(比如又一条 UPDATE),就可能在脏数据基础上操作,最终 COMMIT 时造成逻辑错乱或部分更新成功、部分失败的“半提交”状态。
- 该行为与死锁不同:死锁(错误码 1205)发生时,InnoDB 会主动选择一个事务完整回滚
-
innodb_lock_wait_timeout不影响事务边界,它只是“卡住的那句 SQL 不等了”,不是“这个事务废了” - 连接断开(如网络中断)才会触发自动回滚;超时 ≠ 连接断开
innodb_rollback_on_timeout 控制是否全事务回滚
真正决定“超时后要不要整个事务回滚”的参数是 innodb_rollback_on_timeout,而不是 innodb_lock_wait_timeout。它的默认值是 OFF,即只回滚最后一条语句;设为 ON 后,超时才会触发整个事务回滚。
但要注意:这个参数自 MySQL 5.7.20 起已被废弃,8.0 中彻底移除。官方明确不推荐开启,因为强制全回滚可能掩盖真实问题(比如前几条语句本应独立生效),且破坏应用层对事务状态的控制权。
- 5.7.20+ 版本设置
SET GLOBAL innodb_rollback_on_timeout = ON会静默忽略,无报错也无效果 - 若用的是 5.6 或更早版本,开启后确实会全回滚,但无法解决根本问题——锁等待依然存在,只是错误表现形式变了
- 该参数对死锁无效,死锁回滚由 InnoDB 自动决策,不受此参数影响
为什么不该依赖 innodb_rollback_on_timeout 解决锁等待
调这个参数本质是用“兜底机制”掩盖设计缺陷。锁等待频繁出现,说明业务或 SQL 存在结构性问题,而非超时时间不够长。
- 缺失索引导致扫描行数多、加锁范围大 → 锁等待概率飙升
- 事务里混入非数据库操作(如 HTTP 调用、文件读写)→ 事务空转持锁
- 批量更新未分页、未加
WHERE条件 → 全表锁或间隙锁扩散 - 应用未正确处理
ERROR 1205(死锁)和ERROR 1206(锁超时)的区别,统一重试反而加重竞争
把 innodb_lock_wait_timeout 从 50 改成 300 秒,只会让阻塞持续更久、连接堆积更多、锁扩散更广——它不是解药,是延迟暴露问题的麻醉剂。
应用层必须自己 handle 锁超时
唯一可靠的做法,是在代码里显式捕获锁等待超时错误,并主动回滚 + 重试(或降级)。重点不是“让它别超时”,而是“超时后别让它乱来”。
- 区分错误码:
1205是死锁,通常可立即重试;1206是锁等待超时,需先判断是否真卡住(比如查INFORMATION_SCHEMA.INNODB_TRX看长事务) - 事务边界要清晰:避免在事务中做耗时 I/O,确保
BEGIN到COMMIT/ROLLBACK尽量短 - 用
SELECT ... FOR UPDATE NOWAIT替代默认阻塞式加锁,让冲突快速失败,便于应用控制流 - 监控关键指标:如
Innodb_row_lock_waits、Innodb_row_lock_time_avg,比调参更能定位瓶颈
真正容易被忽略的点是:很多团队花时间调 innodb_lock_wait_timeout,却从没查过 SHOW ENGINE INNODB STATUS\G 里最近的死锁日志——那里才藏着谁锁了谁、为什么锁、怎么改 SQL 的直接证据。











