单看innodb_row_lock_time会误判,因其是启动以来锁等待总毫秒数,无法区分偶发抖动与持续卡顿,需结合innodb_trx和innodb_lock_waits定位谁在等、谁在堵及业务逻辑缺陷。

直接看 Innodb_row_lock_time 没用,它只反映“锁等了多久”,不告诉你“谁在等、为什么等”。必须结合 INNODB_TRX 和 INNODB_LOCK_WAITS 才能定位业务逻辑缺陷。
为什么单看 Innodb_row_lock_time 会误判
这个状态变量是累计值:MySQL 启动以来所有事务锁等待的总毫秒数。它无法区分是偶发慢查询导致的短时抖动,还是某个固定业务路径持续卡顿。比如值突然从 1000 跳到 50000,你不知道是 50 个事务各等 1 秒,还是 1 个事务卡了 49 秒 —— 后者才真正暴露业务问题。
常见误导场景:
- 误以为“值变大 = 并发高”,实际可能是某条未提交的
UPDATE在客户端挂着(比如开发调试时改完没 commit) - 忽略
Innodb_row_lock_time_avg,平均等待时间从 2ms 升到 200ms,说明锁竞争质变,但单看总量可能不明显 - 没对比
Innodb_row_lock_waits,如果等待次数没涨但总耗时飙升,大概率是单个长事务拖累
锁定真实缺陷:查 INNODB_TRX + INNODB_LOCK_WAITS 组合
执行这条查询,直接看到谁在等、谁在堵:
SELECT t1.TRX_ID waiting_trx_id,
t1.TRX_MYSQL_THREAD_ID waiting_thread,
t1.TRX_QUERY waiting_query,
t2.TRX_ID blocking_trx_id,
t2.TRX_MYSQL_THREAD_ID blocking_thread,
t2.TRX_QUERY blocking_query
FROM INFORMATION_SCHEMA.INNODB_TRX t1
INNER JOIN INFORMATION_SCHEMA.INNODB_LOCK_WAITS w ON t1.TRX_ID = w.BLOCKING_TRX_ID
INNER JOIN INFORMATION_SCHEMA.INNODB_TRX t2 ON w.BLOCKING_TRX_ID = t2.TRX_ID;
关键判断点:
-
t2.TRX_STATE是RUNNING(不是LOCK WAIT),且t2.TRX_STARTED时间远早于当前时间 → 极可能是应用层漏了COMMIT或异常退出没 rollback -
t1.TRX_QUERY是简单UPDATE ... WHERE id = ?,但t2.TRX_QUERY是SELECT ... FOR UPDATE加了范围条件 → 说明业务用了非唯一字段做锁条件,触发间隙锁,导致无辜插入被阻塞 - 多次抓取结果中,
blocking_query总是同一类 SQL(如UPDATE order SET status=2 WHERE user_id=?),而waiting_query总是另一类(如INSERT INTO order_log)→ 暴露了业务上“先改主表再写日志”的串行依赖设计缺陷
从监控指标反推业务代码问题
Innodb_row_lock_time 本身不报错,但它和业务行为强相关。以下模式基本对应具体代码缺陷:
- 凌晨 2 点
Innodb_row_lock_time峰值突增 → 查TRX_STARTED发现大量事务始于 1:58,很可能是定时任务批量更新未加LIMIT或未分页,锁住整段索引区间 -
Innodb_row_lock_time_avg持续高于 50ms,但slow_query_log里没慢 SQL → 很可能业务用了SELECT ... FOR UPDATE锁了不该锁的行,或事务里混了 HTTP 调用、文件读写等外部耗时操作 - 锁等待集中在
user_id字段,但该字段无索引 →EXPLAIN会显示type: ALL,实际加的是表级锁,根本不是行锁,Innodb_row_lock_time却在涨(因为 InnoDB 把表锁也计入行锁统计)
真正要盯的不是数字本身,而是那个长时间 RUNNING 的 TRX_ID 对应的应用线程 ID 和代码调用栈 —— 它往往藏在你没加 try/finally 保证 rollback 的那段 DAO 方法里。











