应先查innodb_trx和innodb_lock_waits定位阻塞源,kill blocking_trx_id对应线程;调大innodb_lock_wait_timeout仅延迟失败,不解决锁持有问题,需结合索引优化、事务粒度控制和死锁日志分析根治。

这不是锁机制出问题,而是事务在互相卡住——得先揪出谁在死攥着锁不放
查不到阻塞源,调参全是白忙
报错信息里只有 Lock wait timeout exceeded; try restarting transaction,它不告诉你谁在锁、锁了多久、锁在哪几行。直接改 innodb_lock_wait_timeout 只会让失败来得更晚,但不会让锁变少或变短。
真正该看的是:
-
SELECT * FROM information_schema.INNODB_TRX ORDER BY TRX_STARTED DESC LIMIT 5—— 找出运行时间长、没提交的事务,重点关注TRX_STATE = 'RUNNING'且TRX_ROWS_LOCKED > 0的 -
SELECT * FROM information_schema.INNODB_LOCK_WAITS—— 看BLOCKING_TRX_ID字段,它指向的才是持锁者;别 KILL 报错的那个线程,要 KILL 这个 ID 对应的线程 - 配合
SHOW ENGINE INNODB STATUS\G,重点翻到末尾的LATEST DETECTED DEADLOCK段落,确认是纯等待还是已被 MySQL 自动判定为死锁
UPDATE/DELETE 带子查询=锁表高危操作
这类语句最容易触发全表扫描+长时行锁,尤其当子查询字段没索引时。比如:
UPDATE t SET status = 1 WHERE id IN (SELECT id FROM t2 WHERE x = 1);
如果 t2.x 没索引,子查询慢 → 主查询逐行判断并加锁 → 锁住成千上万行 → 其他事务排队等超时。
解决方式很实在:
- 所有
WHERE、JOIN、IN子句里的字段,必须有索引(复合索引也要覆盖查询条件顺序) - 避免在事务里循环执行单条
UPDATE,改用INSERT ... ON DUPLICATE KEY UPDATE或分页批量处理(如每次 500 行) - 确认没在同一个事务里混用
SELECT FOR UPDATE和后续 DML——前者会提前加锁且锁到事务结束,放大等待面
别混淆 innodb_lock_wait_timeout 和 lock_wait_timeout
这两个参数名字像,但管的事完全不同:
-
innodb_lock_wait_timeout:控制 InnoDB 行锁等待上限,默认 50 秒,只影响 DML(UPDATE/DELETE/INSERT) -
lock_wait_timeout:控制元数据锁(MDL)等待上限,默认 31536000 秒(1 年),主要影响 DDL(ALTER TABLE、DROP INDEX)
你遇到的 Lock wait timeout exceeded 几乎全是前者的问题。临时调大可以救急,但要注意:
-
SET GLOBAL innodb_lock_wait_timeout = 120只对新连接生效,当前连接仍用旧值 - 设太大(比如 600)会让用户干等 10 分钟才失败,体验比立刻报错还差
- 真要用,只应在特定批任务里用
SET SESSION innodb_lock_wait_timeout = 90,执行完立刻恢复
死锁日志默认只留最后一次
很多排查卡在“明明刚发生过死锁,SHOW ENGINE INNODB STATUS 却看不到记录”。因为 MySQL 默认只保留最近一次死锁日志,历史全丢。
必须开这个开关:
SET GLOBAL innodb_print_all_deadlocks = ON;
否则你看到的 LATEST DETECTED DEADLOCK 段落,大概率不是导致当前锁等待的那一次——它可能早被覆盖了。开了之后,所有死锁都会写进错误日志,才能真正回溯根因。
最常被忽略的一点:锁等待超时本身不等于死锁,但两者共享同一套锁管理路径。不打开 innodb_print_all_deadlocks,你就永远在猜。











