MySQL中定位DDL被阻塞的问题处理过程

 更新时间:2026年03月06日 09:37:46   作者:路~~~  
本文介绍了如何判断MySQL DDL是否被阻塞,以及如何定位和解决DDL阻塞问题,通过分析`sys.schema_table_lock_waits`表和`information_schema.innodb_trx`表,可以定位阻塞DDL的会话,并采取相应的解决措施

在生产环境中,执行了一个DDL,发现很久都没有执行完,是不是被阻塞了?要怎么解决?

实际上,如何解决DDL阻塞的问题,是MySQL中一个共性且高频的问题。

下面,就这个问题,给一个清晰明了、拿来即用的解决方案:

  1. 怎么判断一个DDL是不是被阻塞了?
  2. 当DDL被阻塞时,怎么找出阻塞它的会话?

怎么判断一个DDL是不是被阻塞了?

首先,看一个简单的Demo:

session1> create table sbtest.t1(id int primary key,name varchar(10));
Query OK, 0 rows affected (0.02 sec)

session1> insert into sbtest.t1 values(1,'a');
Query OK, 1 row affected (0.01 sec)

session1> begin;
Query OK, 0 rows affected (0.00 sec)

session1> select * from sbtest.t1;
+----+------+
| id | name |
+----+------+
|  1 | a    |
+----+------+
1 row in set (0.00 sec)

session2> alter table sbtest.t1 add c1 datetime;
阻塞中。。。

session3> show processlist;
+----+-----------------+-----------+------+---------+-------+---------------------------------+---------------------------------------+
| Id | User            | Host      | db   | Command | Time  | State                           | Info                                  |
+----+-----------------+-----------+------+---------+-------+---------------------------------+---------------------------------------+
|  5 | event_scheduler | localhost | NULL | Daemon  | 47628 | Waiting on empty queue          | NULL                                  |
| 24 | root            | localhost | NULL | Sleep   |    11 |                                 | NULL                                  |
| 25 | root            | localhost | NULL | Query   |     5 | Waiting for table metadata lock | alter table sbtest.t1 add c1 datetime |
| 26 | root            | localhost | NULL | Query   |     0 | init                            | show processlist                      |
+----+-----------------+-----------+------+---------+-------+---------------------------------+---------------------------------------+
4 rows in set (0.00 sec)

判断一个DDL是不是被阻塞了,很简单,就是执行"show processlist",查看DDL操作对应的状态。

如果显示的是"Waiting for table metadata lock",则意味着这个DDL被阻塞了。

DDL一旦被阻塞了,后续针对该表的所有操作都会被阻塞,都会显示"Waiting for table metadata lock"。这也是DDL让人闻之色变的原因。

碰到类似场景,要么 kill DDL 操作,要么 kill 阻塞 DDL 的会话。

kill DDL 操作是一个治标不治本的方法,毕竟 DDL 操作总要执行。

除此之外,对于 DDL 操作,需要关注元数据库锁的阶段有两个:DDL 开始之初和 DDL 结束之前。如果是后者,在此时 kill DDL 就意味着之前的操作都要回滚,成本相对较高。

所以碰到类似场景,我们一般都会直接 kill 阻塞 DDL 的会话。

那么,怎么知道哪些会话阻塞了 DDL 呢?

下面我们来看看具体的定位方法。

定位方法

方法一:sys.schema_table_lock_waits

sys.schema_table_lock_waits是MySQL 5.7版本引入的,用来定位 DDL 被阻塞的问题。

针对上面这个Demo。

我们看看sys.schema_table_lock_waits的输出。

mysql> select * from sys.schema_table_lock_waits\G
*************************** 1. row ***************************
               object_schema: sbtest
                 object_name: t1
           waiting_thread_id: 62
                 waiting_pid: 25
             waiting_account: root@localhost
           waiting_lock_type: EXCLUSIVE
       waiting_lock_duration: TRANSACTION
               waiting_query: alter table sbtest.t1 add c1 datetime
          waiting_query_secs: 17
 waiting_query_rows_affected: 0
 waiting_query_rows_examined: 0
          blocking_thread_id: 61
                blocking_pid: 24
            blocking_account: root@localhost
          blocking_lock_type: SHARED_READ
      blocking_lock_duration: TRANSACTION
     sql_kill_blocking_query: KILL QUERY 24
sql_kill_blocking_connection: KILL 24
*************************** 2. row ***************************
               object_schema: sbtest
                 object_name: t1
           waiting_thread_id: 62
                 waiting_pid: 25
             waiting_account: root@localhost
           waiting_lock_type: EXCLUSIVE
       waiting_lock_duration: TRANSACTION
               waiting_query: alter table sbtest.t1 add c1 datetime
          waiting_query_secs: 17
 waiting_query_rows_affected: 0
 waiting_query_rows_examined: 0
          blocking_thread_id: 62
                blocking_pid: 25
            blocking_account: root@localhost
          blocking_lock_type: SHARED_UPGRADABLE
      blocking_lock_duration: TRANSACTION
     sql_kill_blocking_query: KILL QUERY 25
sql_kill_blocking_connection: KILL 25
2 rows in set (0.00 sec)

只有一个 alter 操作,却产生了两条记录,而且两条记录的 kill 对象还不一样,其中一条 kill 的对象还是 alter 操作本身。

如果对表结构不熟悉或者不仔细看记录内容的话,难免会 kill 错对象。

不仅如此,在 DDL 操作被阻塞后,如果后续有 N 个查询被 DDL 操作阻塞,还会产生 N2 条记录。

在定位问题时,这 N2 条记录完全是个噪音。

这个时候,就需要我们对上述记录进行过滤了。

过滤的关键是 blocking_lock_type 不等于 SHARED_UPGRADANLE。

SHARED_UPGRADABLE 是一个可升级的共享元数据锁,加锁期间,允许并发查询和更新,常用在 DDL 操作的第一个阶段。

所以,阻塞 DDL 的不会是 SHARED_UPGRADABLE。

故而,针对上面这个case,我们可以通过下面这个查询来精确地定位出需要 kill 的会话。

SELECT sql_kill_blocking_connection
FROM sys.schema_table_lock_waits
WHERE blocking_lock_type <> 'SHARED_UPGRADABLE'
AND waiting_query = 'alter table sbtest.t1 add c1 datetime';

方法二:kill DDL 之前的会话

sys.schema_table_lock_waits是MySQL 5.7才引入的。但在实际生产环境中,MySQL 5.6还是占有相当多的份额。

如何解决MySQL 5.6的这个痛点呢?

细究下来,导致 DDL 被阻塞的操作,无非两类:

1.表上有慢查询未结束。

2.表上有事务未提交。

  • 其中,第一类比较好定位,通过 “show processlist” 就能发现。
  • 而第二类仅凭 “show processlist” 很难定位,因为未提交事务的连接在 “show processlist” 中的状态同空闲连接是一样的,都是 Sleep。
  • 所以,网上有 kill 空闲会话连接的说法,其实也不无道理,但这样做就太简单粗暴了,难免会误杀。
  • 其实,既然是事务, 在 “information_schema.innodb_trx” 中肯定会有记录,如 session1 中的事务,在表中的记录如下:
mysql> select * from information_schema.innodb_trx\G
*************************** 1. row ***************************
                    trx_id: 421568246406360
                 trx_state: RUNNING
               trx_started: 2022-01-02 08:53:50
     trx_requested_lock_id: NULL
          trx_wait_started: NULL
                trx_weight: 0
       trx_mysql_thread_id: 24
                 trx_query: NULL
       trx_operation_state: NULL
         trx_tables_in_use: 0
         trx_tables_locked: 0
          trx_lock_structs: 0
     trx_lock_memory_bytes: 1128
           trx_rows_locked: 0
         trx_rows_modified: 0
   trx_concurrency_tickets: 0
       trx_isolation_level: REPEATABLE READ
         trx_unique_checks: 1
    trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
 trx_adaptive_hash_latched: 0
 trx_adaptive_hash_timeout: 0
          trx_is_read_only: 0
trx_autocommit_non_locking: 0
       trx_schedule_weight: NULL
1 row in set (0.00 sec)

其中 “trx_mysql_thread_id” 是线程 id,结合 “information_schema.processlist”,可进一步缩小范围。

所以,我们可以通过下面这个SQL,定位出执行时间早于 DDL 的事务。

SELECT concat('kill ', i.trx_mysql_thread_id, ';')
FROM information_schema.innodb_trx i, (
    SELECT MAX(time) AS max_time
    FROM information_schema.processlist
    WHERE state = 'Waiting for table metadata lock'
      AND (info LIKE 'alter%'
      OR info LIKE 'create%'
      OR info LIKE 'drop%'
      OR info LIKE 'truncate%'
      OR info LIKE 'rename%'
  )) p
WHERE timestampdiff(second, i.trx_started, now()) > p.max_time;

可喜的是,当前正在执行的查询也会显示在 “information_schema.innodb_trx” 中。

所以,上面这个SQL同样也适用于慢查询未结束的场景。

MySQL 5.7中使用 “sys.schema_table_lock_waits” 的注意事项

“sys.schema_table_lock_waits” 试图依赖一张 MDL 相关的表:“performance_schema.metadata_locks”。

该表是 MySQL 5.7 引入的,会显示 MDL 的相关信息,包括作用对象、锁的类型及锁的状态等。

但在 MySQL 5.7 中,该表默认为空,因为与之相关的 instrument 默认没有开启。MySQL 8.0 才默认开启。

mysql> select * from performance_schema.setup_instruments where name='wait/lock/metadata/sql/mdl';
+----------------------------+---------+-------+
| NAME                       | ENABLED | TIMED |
+----------------------------+---------+-------+
| wait/lock/metadata/sql/mdl | NO      | NO    |
+----------------------------+---------+-------+
1 row in set (0.00 sec)

所以,在 MySQL 5.7 中,如果我们要使用 “sys.schema_table_lock_waits”,必须首先开启 MDL 相关的 instrument。

开启方式很简单,直接修改 “performance_schema.setup_instruments” 表即可。

具体 SQL 如下:

UPDATE performance_schema.setup_instruments SET ENABLED = 'YES', TIMED = 'YES'
WHERE NAME = 'wait/lock/metadata/sql/mdl';

但这种方式是临时生效,实例重启后,又会恢复为默认值。

建议同步修改配置文件:

[mysqld]
performance-schema-instrument='wait/lock/metadata/sql/mdl=ON'

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • MySQL事务的隔离级别详情

    MySQL事务的隔离级别详情

    这篇文章主要介绍了MySQL事务的隔离级别详情,事务隔离级别越高,为避免冲突所花费的性能也就越多,即效率低。在“可重复读”级别,实际上可以解决部分的虚读问题,但是不能防止update更新产生的虚读问题,要禁止虚读产生,还是需要设置串行化隔离级别
    2022-07-07
  • MySql利用父id递归向下查询子节点的方法实例

    MySql利用父id递归向下查询子节点的方法实例

    项目中遇到一个需求,要求查处菜单节点的所有节点,在网上查了一下,大多数的方法用到了存储过程,由于线上环境不能随便添加存储过程,所以自己写一个,这篇文章主要给大家介绍了关于MySql利用父id递归向下查询子节点的相关资料,需要的朋友可以参考下
    2022-03-03
  • mysql中blob数据处理方式

    mysql中blob数据处理方式

    本文通过实例代码给大家介绍了mysql中blob数据处理方式,非常不错,具有一定的参考借鉴价值,需要的朋友参考下吧
    2018-06-06
  • MySQL 一次执行多条语句的实现及常见问题

    MySQL 一次执行多条语句的实现及常见问题

    通常情况MySQL出于安全考虑不允许一次执行多条语句(但也不报错,很让人郁闷)。
    2009-08-08
  • MySQL慢查询优化解决问题

    MySQL慢查询优化解决问题

    这篇文章主要介绍了MySQL慢查询优化解决问题,MySQL的慢查询,全名是慢查询日志,是MySQL提供的一种日志记录,用来记录在MySQL中响应时间超过阀值的语句,下文详细介绍慢查询的调优情况,需要的小伙伴可以参考一下
    2022-03-03
  • MySQL日志UndoLog的作用

    MySQL日志UndoLog的作用

    UndoLog是InnoDB用于事务回滚和MVCC的重要机制,本文主要介绍了MySQL日志UndoLog的作用,文中介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-12-12
  • 计算机管理服务中找不到mysql的服务的解决办法

    计算机管理服务中找不到mysql的服务的解决办法

    MySQL是一种流行的开源关系型数据库管理系统,用于存储和管理大量数据,在计算机管理中,启动MySQL服务是一项重要的任务,因为它可以确保数据库系统的顺利运行,这篇文章主要给大家介绍了关于计算机管理服务中找不到mysql的服务的解决办法,需要的朋友可以参考下
    2023-05-05
  • 真的了解MySQL中的binlog和redolog区别

    真的了解MySQL中的binlog和redolog区别

    MySQL的binlog和redolog都是用于记录数据库操作的日志文件,但是它们有不同的作用和特点,今天给大家分享MySQL的binlog和redolog区别,感兴趣的朋友一起看看吧
    2023-11-11
  • MySQL禁用InnoDB引擎的方法

    MySQL禁用InnoDB引擎的方法

    这篇文章主要介绍了MySQL禁用InnoDB引擎的方法,针对的Mysql版本是5.5和5.6,使用了两种不同的配置文件,需要的朋友可以参考下
    2014-05-05
  • MySQL查询多个字段的最大值的方法

    MySQL查询多个字段的最大值的方法

    MySQL作为最流行的关系型数据库管理系统之一,在数据存储与查询方面拥有广泛的应用,本文将深入探讨MySQL中查询多个字段最大值的方法,并通过实际案例展示这些方法在不同场景下的应用,感兴趣的朋友跟随小编一起看看吧
    2026-01-01

最新评论