UPDATE卡住90%是锁等待而非慢查询,应优先查INNODB_TRX中TRX_STATE='LOCK WAIT'、TRX_WAITING_TRX_ID非空、TRX_STARTED异常早的事务,并结合INNODB_LOCK_WAITS或performance_schema.data_lock_waits定位阻塞源。

UPDATE 卡住不动,90% 是锁等待,不是慢查询;直接查 INNODB_TRX 和 INNODB_LOCK_WAITS 就能定位真凶,别先优化 SQL 或调参数。
怎么一眼确认是不是被锁住了
执行 SELECT * FROM information_schema.INNODB_TRX ORDER BY TRX_STARTED DESC LIMIT 5;,重点看三列:
-
TRX_STATE是'LOCK WAIT':说明它正在等锁,不是自己慢 -
TRX_WAITING_TRX_ID非空:这个 ID 就是它在等的事务,拿去查INNODB_TRX找源头 -
TRX_STARTED时间很早(比如几分钟前),但状态还是RUNNING:大概率是长事务没提交,成了“锁源”
如果 INNODB_LOCK_WAITS 查询结果为空,不代表没锁——只是锁刚释放或还没形成等待链。卡顿发生时必须立刻查,快照不持久。
WHERE 条件没走索引,UPDATE 就会锁全表
InnoDB 的 UPDATE 在无法用索引定位行时,会扫描全表并逐行加记录锁 + 间隙锁,锁范围远超预期。别人插数据、改相邻行、甚至查某个范围,全得排队。
- 用
EXPLAIN FORMAT=JSON SELECT *模拟你的 WHERE 条件,检查key字段是否为NULL,rows_examined是否异常高 - 避免对字段做函数操作:
WHERE DATE(create_time) = '2024-01-01'→ 改成WHERE create_time >= '2024-01-01' AND create_time < '2024-01-02' - 联合索引失效常见场景:
INDEX(status, updated_at),但写WHERE updated_at > 'xxx'就完全用不上
KILL 前必须看 TRX_ROWS_MODIFIED
盲目 KILL 可能引发长回滚,让锁持有更久。关键看 TRX_ROWS_MODIFIED:
- 值为
0且TRX_STATE = 'LOCK WAIT':纯等待者,可安全KILL QUERY - 值很大(如几万)且持续增长:它自己就是锁源,KILL 后要回滚大量数据,风险高
- 优先用
KILL QUERY <code>thread_id中断语句,而不是KILL <code>thread_id断连接
别只信 SHOW PROCESSLIST 的 Time 列——sleeping 状态的连接可能刚开启事务还没执行任何语句,得结合 TRX_STARTED 和 TRX_ROWS_MODIFIED 综合判断。
MySQL 8.0+ 推荐用 performance_schema.data_lock_waits
旧的 INNODB_LOCKS 表已在 8.0 移除,data_lock_waits 字段更清晰、支持按库过滤、能关联具体行锁对象:
SELECT r.OBJECT_NAME, r.LOCK_MODE AS requested_mode, b.LOCK_MODE AS blocking_mode, r.OWNER_THREAD_ID, b.OWNER_THREAD_ID FROM performance_schema.data_lock_waits w JOIN performance_schema.data_locks r ON w.REQUESTING_ENGINE_LOCK_ID = r.ENGINE_LOCK_ID JOIN performance_schema.data_locks b ON w.BLOCKING_ENGINE_LOCK_ID = b.ENGINE_LOCK_ID WHERE r.OBJECT_SCHEMA = 'your_db';
注意需确认 performance_schema 已启用,且 data_locks 和 data_lock_waits 的 consumers 处于 ENABLED 状态(默认通常开启)。
隐式锁、MDL 锁、DDL 阻塞这些场景不会出现在 INNODB_LOCK_WAITS 里,但会卡住 UPDATE 却不报错——遇到等超 50 秒还不抛 Lock wait timeout exceeded,就得查 metadata_locks 或 SHOW ENGINE INNODB STATUS\G 的 TRANSACTIONS 段。


















