锁等待超时不是数据库故障,而是update被阻塞:需立即查innodb_trx和innodb_lock_waits定位阻塞源,确认是否因索引缺失导致行锁退化为表锁,或长事务未提交持有锁。

锁等待超时不是数据库坏了,而是你的 UPDATE 被卡在等锁——大概率是别人拿着锁不放,或者你自己没走索引,把行锁退化成表锁了。
查谁在 hold 锁:立刻查 INNODB_TRX 和 INNODB_LOCK_WAITS
错误刚报出来时,别翻 SHOW ENGINE INNODB STATUS,它只留最后一次快照,早被覆盖。直接跑这个联查:
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_TRX r
INNER JOIN INFORMATION_SCHEMA.INNODB_LOCK_WAITS w ON r.trx_id = w.requesting_trx_id
INNER JOIN INFORMATION_SCHEMA.INNODB_TRX b ON b.trx_id = w.blocking_trx_id;
关键看三列:blocking_query 是真正在堵路的 SQL;blocking_thread 是它的线程 ID;waiting_query 是你失败的那条 UPDATE。如果查不到结果,说明锁已释放,得靠日志倒推时间点。
-
TRX_STATE = 'RUNNING'且TRX_STARTED很早的事务,优先怀疑 -
TRX_STATE = 'LOCK WAIT'的是受害者,不是元凶 -
TRX_ROWS_LOCKED值极大(比如上百万),基本可断定没走索引、在扫全表
为什么 UPDATE 会锁全表:检查 WHERE 条件是否命中索引
InnoDB 行锁依赖索引。WHERE 字段没索引,引擎只能全表扫描,每扫一行就加一把锁——等于给整张表上了行锁,高并发下就是排队等死。
验证方法就一条命令:
EXPLAIN SELECT * FROM your_table WHERE your_condition;
重点看 type 列:
-
ALL或index→ 全表/全索引扫描,危险 -
range、ref、const→ 走了索引,大概率安全 - 注意隐式转换:
user_id是BIGINT,但传了字符串'123',索引直接失效 - 联合索引顺序很重要:
WHERE status = ? AND created_at > ?应建(status, created_at),反过来效果差很多
批量 UPDATE 别硬扛:拆事务、移出耗时操作
一个事务里更新 10 万行,锁就挂 10 万行整整几十秒。哪怕逻辑再简单,只要事务没提交,锁就不放。
- 分片执行:每 100~500 行一个
UPDATE+COMMIT,用WHERE id BETWEEN ? AND ?或子查询分页 - 别在事务里调 HTTP、发邮件、写大文件——这些操作移出事务块,改用消息队列或最终一致性补偿
- 避免“先
SELECT FOR UPDATE查一堆数据,算十分钟逻辑,再UPDATE”:改成先查 ID 列表,计算完后直接UPDATE ... WHERE id IN (...) - 慎用
REPEATABLE READ隔离级别:高并发下间隙锁范围大,容易锁住不该锁的范围;临时切到READ COMMITTED可快速缓解
杀连接要小心:KILL thread_id 不等于立刻解围
看到长事务就 KILL?风险很大。如果它已经写了 80% 的数据,KILL 会触发回滚,磁盘 IO 暴涨,锁反而持续更久。
- 先确认它是不是业务关键流程(比如财务对账)——别一上来就杀
- 优先尝试让它主动提交或回滚:联系对应服务负责人,看能否人工触发
- 必须杀时,用
KILL <code>thread_id,不是KILL QUERY(后者只中断当前语句,事务还在) -
innodb_lock_wait_timeout别乱调大:设成 300 秒,用户就得卡五分钟才失败,体验比立刻失败还差
真正难缠的从来不是单条慢 UPDATE,而是那些开了事务却忘了提交的“静默连接”——SHOW PROCESSLIST 里 Command = 'Sleep'、Time 很大、Info 为空,这种才是最该盯紧的。











