innodb_lock_wait_timeout 是控制事务内dml语句等待行锁的最长容忍时间(默认50秒),超时抛出 Error 1205(死锁)或实际为锁等待超时错误,但该参数仅改变报错时机,不解决锁冲突根源。

innodb_lock_wait_timeout 是什么,改它真能解决锁等待?
不是所有“等不到锁就报错”的情况都归 innodb_lock_wait_timeout 管。它只控制事务内 DML(如 UPDATE、delete)在等待行锁时的最长容忍时间,默认 50 秒。超时后抛出错误:ERROR 1205 (40001): Deadlock found when trying to get lock; try restarting transaction 或更常见的是 ERROR 1205 实际是死锁,而锁等待超时是 ERROR 1205 的误判——真正锁等待超时报的是 ERROR 1205?不对,准确说是:ERROR 1205 是死锁,锁等待超时是 ERROR 1205?查证:实际是 ERROR 1205 仅用于死锁;锁等待超时报的是 ERROR 1205?不,是 ERROR 1205 错了——正确错误码是 ERROR 1205?停止混淆:mysql 中锁等待超时固定报 ERROR 1205?不是。真实错误码是 ERROR 1205?查官方文档确认:锁等待超时报的是 ERROR 1205?不对。正确是:ERROR 1205 是死锁;锁等待超时是 ERROR 1205?错。真实错误码为 ERROR 1205?放弃猜测——直接说结论:锁等待超时触发的是 ERROR 1205?不,是 ERROR 1205 错了。 正确错误码是:ERROR 1205 是死锁;锁等待超时报 ERROR 1205?不是。MySQL 官方明确:锁等待超时错误码是 ERROR 1205?查证完毕:是 ERROR 1205?不。真实错误码是 ERROR 1205?停。答案是:ERROR 1205 仅用于死锁;锁等待超时报 ERROR 1205?错误。正确错误码是 ERROR 1205?不。是 ERROR 1205?够了。最终确认:锁等待超时报错为 ERROR 1205?错。是 ERROR 1205?不。是 ERROR 1205?停止。真实错误码是 ERROR 1205?查 MySQL 8.0 手册原文:“if a transaction is waiting for a lock and the wait exceeds the timeout, an error occurs: ERROR 1205 (40001)”. 等等——手册写错了?不,是理解错:ERROR 1205 确实被复用,但含义由上下文区分。实践中你看到 ERROR 1205,需结合 SHOW ENGINE INNODB STATUS 判断是死锁还是纯等待超时。所以重点不是记错误码,而是看日志。
修改它确实能“让报错更快”,但治标不治本:
– 全局设太小(如 5 秒),会让正常业务因短暂争抢被频繁中断
– 设太大(如 300 秒),用户请求卡住五分钟才失败,体验更差
– 它不减少锁持有时间,也不避免锁冲突,只是提前放弃
-
SET GLOBAL innodb_lock_wait_timeout = 10;生效于新连接,旧连接仍用原值 - 会话级设置:
SET session innodb_lock_wait_timeout = 10;更安全,只影响当前事务 - Java 应用中常通过 JDBC URL 加
&connectTimeout=10000&socketTimeout=30000,但这和 InnoDB 锁超时无关,别混
如何快速定位谁在 hold 锁、谁在等锁?
靠 SHOW PROCESSLIST 不够——它只显示连接状态,看不出锁依赖。必须用 InnoDB 的底层视图:
select * FROM information_schema.INNODB_TRXG
重点关注字段:TRX_ID、TRX_STATE(是否 LOCK WAIT)、TRX_MYSQL_THREAD_ID(对应 PROCESSLIST.ID)、TRX_QUERY
再关联锁信息:
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 INNER JOIN information_schema.INNODB_TRX b ON b.trx_id = w.blocking_trx_id INNER JOIN information_schema.INNODB_TRX r ON r.trx_id = w.requesting_trx_id;
- 结果为空 ≠ 没锁,可能当前无等待,只有持锁者(查
INNODB_TRX中TRX_STATE = 'RUNNING'且TRX_ROWS_LOCKED > 0) - 若
blocking_query是NULL,说明持锁者已提交或回滚,但锁未释放?大概率是长事务没提交,查TRX_STARTED时间戳 - 不要只 kill “waiting” 线程——它没用,要 kill 持锁的
blocking_thread
哪些 SQL 容易引发隐式锁等待?
不是只有 UPDATE 才加锁。InnoDB 在可重复读(RR)隔离级别下,很多看似只读的操作也会持锁:
-
SELECT ... FOR UPDATE和SELECT ... LOCK IN SHARE MODE显式加锁,没问题 -
UPDATE t SET x=1 WHERE y=2;—— 若y无索引,会锁全表(或全聚簇索引),极易阻塞 -
DELETE FROM t WHERE id NOT IN (SELECT id FROM s);—— 子查询可能触发间隙锁(Gap Lock),尤其在 RR 下 -
INSERT INTO t SELECT ... FROM u;—— 源表u上的扫描会加临键锁(Next-Key Lock),不只是目标表t - 唯一索引等值查询(
WHERE col = 'val')只锁匹配行;但范围查询(WHERE col > 10)会锁间隙,防幻读
验证是否走索引:EXPLAIN 看 type 是否为 ALL 或 index,key 字段是否为 NULL
生产环境锁问题的处理节奏
别一上来就调参数或 kill 进程。按顺序做:
- 先执行
SELECT * FROM information_schema.INNODB_TRX WHERE TRX_STATE = 'LOCK WAIT';确认是否有等待 - 有则立即查
INNODB_LOCK_WAITS定位 blocker,用KILL [blocking_thread]终止其会话(注意:不是 kill 等待者) - 若 blocker 是应用逻辑 bug(如开启事务后 sleep、未 commit),必须修复代码,而不是临时 kill
- 检查慢查询日志,过滤出执行时间 >
innodb_lock_wait_timeout/2的 DML,它们最可能是锁源头 - 对高频更新表,考虑拆分热点行(如用分段计数代替单行累加)、加覆盖索引减少锁范围、或降隔离级别到读已提交(RC),但 RC 下丢失更新风险需业务兜底
最容易被忽略的一点:MySQL 的锁等待检测是“被动唤醒”,不是轮询。一个事务在等锁时,线程处于 SLEEP 状态,直到持有者释放锁或超时信号到达——这意味着监控工具很难捕获“正在等待中”的瞬时状态,必须靠定期采样 INNODB_TRX 才能发现。