排查锁等待:谁在阻塞谁
线上出现锁等待时,第一步不是重启数据库,而是找出谁持有锁、谁在等谁。MySQL 8.0 用 performance_schema 的 data_locks 与 data_lock_waits 给出等待关系,配合 information_schema.INNODB_TRX 看事务状态与开始时间;PostgreSQL 用 pg_locks、pg_stat_activity 与 pg_blocking_pids() 直接拿到阻塞者 pid。定位到具体会话后,再判断是等待、取消还是终止,并从应用侧修掉根因。
基本信息
| MySQL 等待关系 | performance_schema.data_lock_waits、data_locks(8.0 起;5.7 为 INNODB_LOCK_WAITS) |
|---|---|
| MySQL 事务视图 | information_schema.INNODB_TRX,含 trx_started 与 trx_rows_locked |
| PostgreSQL | pg_locks + pg_stat_activity,pg_blocking_pids(pid) 直接返回阻塞者 |
| 排查顺序 | 先看等待者,再顺藤摸到阻塞者,最后落到应用语句 |
| 处置原则 | 优先取消或 kill 长事务,重启数据库是最后手段 |
典型使用场景
- 线上「查询变慢」时先确认是不是锁等待,而不是急着扩容或加索引
- 批量变更前预检:确认没有长事务占用目标表的行
- 定位连接池里未提交的事务,找出 Sleep 却持有锁的会话
- 排查报错 1205(MySQL 锁等待超时)与 55P03(PG 拿不到锁)
- 死锁发生后复查残留锁与受影响事务,确认业务已恢复正常
MySQL 排查
MySQL 排查
SELECT * FROM performance_schema.data_lock_waits\G # 等待关系:谁等谁
SELECT trx_id, trx_state, trx_started, trx_mysql_thread_id,
trx_rows_locked, left(trx_query, 60) AS query
FROM information_schema.INNODB_TRX
ORDER BY trx_started; # 开始最早的往往就是阻塞源
SELECT id, user, db, command, time, state, left(info, 60)
FROM information_schema.PROCESSLIST
WHERE command <> 'Sleep'
ORDER BY time DESC; # 长跑语句一览PostgreSQL 排查
PostgreSQL 排查
SELECT pid, wait_event_type, wait_event, state, query_start, left(query, 60)
FROM pg_stat_activity
WHERE wait_event_type = 'Lock'; # 所有在等锁的会话
SELECT pid, pg_blocking_pids(pid) AS blocked_by, left(query, 60)
FROM pg_stat_activity
WHERE cardinality(pg_blocking_pids(pid)) > 0; # 每个等待者被谁挡住
SELECT pg_cancel_backend(12345); # 温和:请求取消该会话当前语句
SELECT pg_terminate_backend(12345); # 强制:断开整个会话常见等待线索读法
| 线索 | 含义 | 处置方向 |
|---|---|---|
| data_lock_waits 有记录 / blocked_by 非空 | 确实存在锁等待关系 | 顺着阻塞者 pid 或 thread_id 查会话 |
| INNODB_TRX 中 trx_started 很早 | 长事务一直未提交 | 联系应用确认,必要时 kill |
| 会话状态为 Sleep 却持有锁 | 连接池里未提交的事务 | 检查应用事务边界与连接归还逻辑 |
| wait_event 为 Lock 但耗时极短 | 正常的瞬时竞争 | 观察频率,不必干预 |
MySQL 5.7 没有 performance_schema.data_lock_waits(8.0 才引入),老版本改用 sys.innodb_lock_waits 视图或 information_schema.INNODB_LOCK_WAITS,注意字段名与视图名都不同。
kill 之前先确认它是不是唯一的持锁者:批量 kill 会把长事务与正常事务一起清掉,引发应用大规模重试;优先用 KILL QUERY 或 pg_cancel_backend 取消语句,无效再终止会话。