最快路径是查询 information_schema.INNODB_TRX 并按 TRX_STARTED 排序,重点关注 TRX_STATE 为 RUNNING 的事务及其 TRX_MYSQL_THREAD_ID、TRX_QUERY 和锁行数。

如何快速定位正在阻塞 UPDATE 的事务
MySQL 中 UPDATE 被卡住,大概率是被另一个未提交的事务锁住了行或间隙。直接查 information_schema.INNODB_TRX 是最快路径,它能立刻告诉你谁在跑、跑了多久、锁了什么。
-
SELECT * FROM information_schema.INNODB_TRX ORDER BY TRX_STARTED;—— 按开始时间排序,排在最前面的往往是“元凶” - 重点关注
TRX_STATE(是否 RUNNING 或 LOCK WAIT)、TRX_MYSQL_THREAD_ID(线程 ID)、TRX_QUERY(当前执行语句)、TRX_ROWS_LOCKED和TRX_ROWS_MODIFIED - 如果看到多个事务状态为
LOCK WAIT,说明存在级联等待,要顺着TRX_WAITING_FOR_LOCK(需查INNODB_LOCK_WAITS)往上追,但通常先杀掉最早那个 RUNNING 的就解了
为什么 SHOW PROCESSLIST 看不到真正的问题事务
SHOW PROCESSLIST 只显示客户端连接状态和当前 SQL,对已启动但没发新语句的事务(比如 BEGIN 后只执行了一条 SELECT 就挂起)完全不敏感。这类“静默事务”不会出现在 Processlist 的 State 列里,却持续持有锁。
- 它的
Command是Sleep,Time可能很大,但Info为空——这恰恰是危险信号 - 必须关联
INNODB_TRX.TRX_MYSQL_THREAD_ID和PROCESSLIST.ID才能确认哪个 Sleep 连接对应着活跃事务 - 一个常见误判:只 kill 掉
State = "Locked"的连接,其实它只是受害者;真该 kill 的是那个State = "Sleep"却TRX_STATE = "RUNNING"的线程
安全地 kill 长事务而不是简单 kill connection
用 KILL <code>thread_id 是有效的,但要注意:如果事务已写入大量数据,kill 会触发回滚,可能让磁盘 IO 暴涨、锁持续更久。优先考虑先尝试让它主动提交或回滚。
- 先查该线程最近在做什么:
SELECT * FROM performance_schema.events_statements_current WHERE THREAD_ID = <code>xxx;(需开启events_statements_current消费器) - 若确认无业务逻辑依赖,再执行
KILL <code>thread_id;不要用KILL QUERY,它只中断当前语句,事务仍存活 - 生产环境建议加超时控制:应用层设置
innodb_lock_wait_timeout(默认 50 秒),比等 50 秒再报错更可控;也可临时调低:SET SESSION innodb_lock_wait_timeout = 5;
如何避免下次又被锁住——从隔离级别和索引说起
Repeatable Read 下的间隙锁(Gap Lock)经常是隐式锁源,尤其 UPDATE 带范围条件却没走索引时,会升级为全表扫描+全表加锁。
- 检查你的 UPDATE 是否命中索引:
EXPLAIN FORMAT=tree <code>UPDATE ...;(MySQL 8.0+)或EXPLAIN <code>SELECT ...类比 - 避免在 RR 级别下对非唯一字段做范围 UPDATE;如必须,考虑临时切到
READ COMMITTED(需评估一致性影响) - 应用层加锁要短:BEGIN → 查询 → 计算 → UPDATE → COMMIT,中间别夹 HTTP 调用或 sleep
- 监控可落地:定期查
SELECT TRX_ID, TRX_STARTED, TIME_TO_SEC(NOW() - TRX_STARTED) AS duration FROM INNODB_TRX WHERE TIME_TO_SEC(NOW() - TRX_STARTED) > 60;,告警并自动采样
真正难处理的不是锁本身,而是那些开了事务却不提交、也不 rollback 的连接——它们藏得深、杀得疼、恢复慢。盯住 TRX_STATE = "RUNNING" 且 TRX_STARTED 很老的记录,比等超时更有意义。


















