当业务高峰出现大量连接堆积、SQL 执行时间突然拉长时,多数情况下不是 SQL 本身变慢,而是目标表或目标行被其他事务锁住了。MySQL 并没有提供一条类似 SHOW LOCKED TABLES 的单命令,但通过 performance_schema、sys 和 information_schema 的组合查询,可以非常精确地看到锁持有者、等待者和被锁定的表名。实际排查时,需要先理清不同锁类型在元数据视图中的分布,再使用对应的查询语句。

先理清 MySQL 的锁类型与对应数据来源
MySQL 中的锁并不只有行锁。按粒度可以分为表锁、行锁和页锁;按用途可以分为共享锁、排他锁、意向锁;此外还有 DDL 与 DML 之间互相阻塞的元数据锁 MDL。不同类型的信息分散在不同的系统视图里,如果一开始就用错视图,很容易查了半天也找不到阻塞源头。
performance_schema.data_locks 用来记录 InnoDB 事务级别的行锁和意向锁信息,performance_schema.metadata_locks 用来记录元数据锁信息,information_schema.innodb_trx 则记录每个活跃事务的基本情况,而 sys.innodb_lock_waits 是官方封装好的视图,会把锁等待关系整理成更容易阅读的结果。
在 MySQL 5.7 或更早版本中,performance_schema 相关的锁等待消费者可能没有默认开启,需要确认 setup_consumers 中相关配置是否启用。对于 MySQL 8.0 来说,绝大多数情况下这些视图已经可以直接查询,不需要额外调整。
通过 performance_schema.data_locks 查看被锁定的表
要查行锁到底锁在哪些表上,最直接的方式是查询 performance_schema.data_locks。这个视图里有一个 OBJECT_NAME 字段,对应被锁定的表名;LOCK_TYPE 可以区分表级锁和行级锁,LOCK_MODE 则会显示 IX、X、S 等锁模式,LOCK_STATUS 用于判断锁是已经被授予还是正在等待。
SELECT engine, object_schema, object_name, lock_type, lock_mode, lock_status, lock_data FROM performance_schema.data_locks;
如果查询结果中同一张表同时出现了 GRANTED 状态的 X 锁和另一个 WAITING 状态的 X 锁,就说明这里有明确的锁等待关系。此时你已经知道了被锁定的表名,但还需要继续定位哪个会话在等待、哪个会话在阻塞。可以继续查询 data_lock_waits 获取事务 ID 之间的等待关系。
SELECT requesting_engine_transaction_id AS waiting_trx, blocking_engine_transaction_id AS blocking_trx, requesting_engine_lock_id AS waiting_lock, blocking_engine_lock_id AS blocking_lock FROM performance_schema.data_lock_waits;
拿到事务 ID 之后,还需要回到 data_locks 或 innodb_trx 里继续关联线程 ID 和 SQL 文本。这个过程比较繁琐,更适合在需要分析锁的具体类型和加锁范围时使用。如果只是想快速定位是哪个会话阻塞了业务,使用 sys.innodb_lock_waits 会高效得多。
使用 sys.innodb_lock_waits 快速定位阻塞会话
手动关联 data_locks 和 data_lock_waits 虽然完整,但字段多、步骤多。sys 库里的 innodb_lock_waits 视图已经把这些信息做了串联,直接展示等待事务 ID、阻塞事务 ID、等待的线程号、阻塞的线程号以及对应的 SQL 文本。
SELECT waiting_trx_id, waiting_pid, waiting_query, blocking_trx_id, blocking_pid, blocking_query, wait_age FROM sys.innodb_lock_waits;
查询结果中,waiting_pid 是等待锁的线程号,blocking_pid 是持有锁的线程号。waiting_query 和 blocking_query 通常会展示正在执行或最后执行的 SQL,这对判断业务逻辑非常有帮助。拿到 blocking_pid 后,可以回到 information_schema.processlist 查看这个线程已经运行了多久、当前处于什么状态。
SELECT id, user, host, db, command, time, state, info FROM information_schema.processlist WHERE id = 123;
使用时把 123 替换成实际的 blocking_pid。如果发现这个线程是一个长时间未提交的空闲事务,或者是一条执行很久的大事务,就需要评估是否要 KILL 掉它。需要注意的是,KILL 会回滚该事务已经执行过的修改,在高并发业务中必须谨慎操作,最好先与相关业务方确认。
元数据锁造成的 ALTER TABLE 长时间无响应
除了 InnoDB 行锁,还有一种常见场景是 ALTER TABLE 长时间无法完成,线程状态一直显示为 Waiting for table metadata lock。这通常不是行锁问题,而是元数据锁 MDL 导致的。DDL 语句需要获取表级排他元数据锁,只要这张表上还有未提交的事务,DDL 请求就只能排队等待。
要定位这类问题,可以查询 performance_schema.metadata_locks,重点看 lock_status 为 PENDING 的记录,这些就是正在等待元数据锁的线程。
SELECT object_schema, object_name, lock_type, lock_status, owner_thread_id FROM performance_schema.metadata_locks WHERE object_type = 'TABLE' AND lock_status = 'PENDING';
有时候这个查询结果为空,可能是因为 performance_schema 的元数据锁 instrument 没有启用。此时可以检查 setup_consumers 中与 wait/lock/metadata/sql/mdl 相关的配置。更直接的兜底方式是执行 SHOW FULL PROCESSLIST,找到状态为 Waiting for table metadata lock 的线程,再回到同一张表上排查是否存在长时间未提交的事务。
SHOW PROCESSLIST 与 innodb_trx 配合排查
如果前面的视图因为权限不足或版本限制无法使用,SHOW FULL PROCESSLIST 是最基础的排查工具。关注 State 列中包含 lock 的记录,例如 Waiting for table metadata lock、Waiting for global read lock,以及 Updating 状态且耗时异常长的线程。Info 列会展示 SQL 文本,Id 列就是线程 ID。
SHOW FULL PROCESSLIST;
与此同时,可以查询 information_schema.innodb_trx 查看当前所有活跃事务,通过事务开始时间和锁定的行数来判断哪个事务最可疑。
SELECT trx_id, trx_state, trx_started, trx_mysql_thread_id, trx_query, trx_rows_locked, trx_rows_modified FROM information_schema.innodb_trx ORDER BY trx_started;
其中 trx_started 越早的事务越有可能是阻塞源。结合 information_schema.processlist 中的 Time 字段,可以判断一个事务从开始到现在持有了多长时间。不要一看到长事务就立刻 KILL,先通过 trx_query 判断它是在执行大批量写入,还是已经进入空闲但未提交状态。空闲事务在 SHOW PROCESSLIST 中通常表现为 Command 为 Sleep 且 Info 为 NULL,这类事务很多是应用层忘记提交或连接池中的长事务造成,杀掉的风险通常较低,但仍然需要与业务确认后再操作。