mysql中锁的等待与超时配置与解决

2次阅读

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

mysql中锁的等待与超时配置与解决

innodb_lock_wait_timeout 是什么,改它真能解决锁等待?

不是所有“等不到锁就报错”的情况都归 innodb_lock_wait_timeout 管。它只控制事务内 DML(如 UPDATEdelete)在等待行锁时的最长容忍时间,默认 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_IDTRX_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_TRXTRX_STATE = 'RUNNING'TRX_ROWS_LOCKED > 0
  • blocking_queryNULL,说明持锁者已提交或回滚,但锁未释放?大概率是长事务没提交,查 TRX_STARTED 时间戳
  • 不要只 kill “waiting” 线程——它没用,要 kill 持锁的 blocking_thread

哪些 SQL 容易引发隐式锁等待?

不是只有 UPDATE 才加锁。InnoDB 在可重复读(RR)隔离级别下,很多看似只读的操作也会持锁:

  • SELECT ... FOR UPDATESELECT ... 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)会锁间隙,防幻读

验证是否走索引:EXPLAINtype 是否为 ALLindexkey 字段是否为 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 才能发现。

text=ZqhQzanResources