在上篇文章《MySQL表结构变更,不可不知的Metadata Lock》中,我们介绍了MDL引入的背景,及基本概念,从“道”的层面知道了什么是MDL。下面就从“术”的层面看看如何定位MDL的相关问题。

在MySQL 5.7中,针对MDL,引入了一张新表performance_schema.metadata_locks,该表可对外展示MDL的相关信息,包括其作用对象,类型及持有等待情况。

开启MDL的instrument

但是相关instrument并没有开启(MySQL 8.0是默认开启的),其可通过如下两种方式开启,

临时生效

修改performance_schema.setup_instrume nts表,但实例重启后,又会恢复为默认值。

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'

测试场景

下面结合一个简单的Demo,来看看在MySQL 5.7中如何定位DDL操作的阻塞问题。

session1> begin;
Query OK, 0 rows affected (0.00 sec) session1> delete from slowtech.t1 where id=2;
Query OK, 1 row affected (0.00 sec) session1> select * from slowtech.t1;
+------+------+
| id | name |
+------+------+
| 1 | a |
+------+------+
1 row in set (0.00 sec) session1> update slowtech.t1 set name='c' where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0 session2> alter table slowtech.t1 add c1 int; ##被阻塞 session3> show processlist;
+----+------+-----------+------+---------+------+---------------------------------+------------------------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+-----------+------+---------+------+---------------------------------+------------------------------------+
| 2 | root | localhost | NULL | Sleep | 51 | | NULL |
| 3 | root | localhost | NULL | Query | 0 | starting | show processlist |
| 4 | root | localhost | NULL | Query | 9 | Waiting for table metadata lock | alter table slowtech.t1 add c1 int |
+----+------+-----------+------+---------+------+---------------------------------+------------------------------------+
3 rows in set (0.00 sec) session3> select object_type,object_schema,object_name,lock_type,lock_duration,lock_status,owner_thread_id from performance_schema.metadata_locks;
+-------------+--------------------+----------------+---------------------+---------------+-------------+-----------------+
| object_type | object_schema | object_name | lock_type | lock_duration | lock_status | owner_thread_id |
+-------------+--------------------+----------------+---------------------+---------------+-------------+-----------------+
| TABLE | slowtech | t1 | SHARED_WRITE | TRANSACTION | GRANTED | 27 |
| GLOBAL | NULL | NULL | INTENTION_EXCLUSIVE | STATEMENT | GRANTED | 29 |
| SCHEMA | slowtech | NULL | INTENTION_EXCLUSIVE | TRANSACTION | GRANTED | 29 |
| TABLE | slowtech | t1 | SHARED_UPGRADABLE | TRANSACTION | GRANTED | 29 |
| TABLE | slowtech | t1 | EXCLUSIVE | TRANSACTION | PENDING | 29 |
| TABLE | performance_schema | metadata_locks | SHARED_READ | TRANSACTION | GRANTED | 28 |
+-------------+--------------------+----------------+---------------------+---------------+-------------+-----------------+
6 rows in set (0.00 sec)

这里,重点关注lock_status,"PENDING"代表线程在等待MDL,而"GRANTED"则代表线程持有MDL。

如何找出引起阻塞的会话

结合owner_thread_id,可以可到,是29号线程在等待27号线程的MDL,此时,可kill掉52号线程。

但需要注意的是,owner_thread_id给出的只是线程ID,并不是show processlist中的ID。如果要查找线程对应的processlist id,需查询performance_schema.threads表。

session3> select * from performance_schema.threads where thread_id in (27,29)\G
*************************** 1. row ***************************
THREAD_ID: 27
NAME: thread/sql/one_connection
TYPE: FOREGROUND
PROCESSLIST_ID: 2
PROCESSLIST_USER: root
PROCESSLIST_HOST: localhost
PROCESSLIST_DB: NULL
PROCESSLIST_COMMAND: Sleep
PROCESSLIST_TIME: 214
PROCESSLIST_STATE: NULL
PROCESSLIST_INFO: NULL
PARENT_THREAD_ID: 1
ROLE: NULL
INSTRUMENTED: YES
HISTORY: YES
CONNECTION_TYPE: Socket
THREAD_OS_ID: 9800
*************************** 2. row ***************************
THREAD_ID: 29
NAME: thread/sql/one_connection
TYPE: FOREGROUND
PROCESSLIST_ID: 4
PROCESSLIST_USER: root
PROCESSLIST_HOST: localhost
PROCESSLIST_DB: NULL
PROCESSLIST_COMMAND: Query
PROCESSLIST_TIME: 172
PROCESSLIST_STATE: Waiting for table metadata lock
PROCESSLIST_INFO: alter table slowtech.t1 add c1 int
PARENT_THREAD_ID: 1
ROLE: NULL
INSTRUMENTED: YES
HISTORY: YES
CONNECTION_TYPE: Socket
THREAD_OS_ID: 9907
2 rows in set (0.00 sec)

将这两张表结合,借鉴sys.innodb_lock _waits的输出,实际上我们也可以直观地呈现MDL的等待关系。

SELECT
a.OBJECT_SCHEMA AS locked_schema,
a.OBJECT_NAME AS locked_table,
"Metadata Lock" AS locked_type,
c.PROCESSLIST_ID AS waiting_processlist_id,
c.PROCESSLIST_TIME AS waiting_age,
c.PROCESSLIST_INFO AS waiting_query,
c.PROCESSLIST_STATE AS waiting_state,
d.PROCESSLIST_ID AS blocking_processlist_id,
d.PROCESSLIST_TIME AS blocking_age,
d.PROCESSLIST_INFO AS blocking_query,
concat('KILL ', d.PROCESSLIST_ID) AS sql_kill_blocking_connection
FROM
performance_schema.metadata_locks a
JOIN performance_schema.metadata_locks b ON a.OBJECT_SCHEMA = b.OBJECT_SCHEMA
AND a.OBJECT_NAME = b.OBJECT_NAME
AND a.lock_status = 'PENDING'
AND b.lock_status = 'GRANTED'
AND a.OWNER_THREAD_ID <> b.OWNER_THREAD_ID
AND a.lock_type = 'EXCLUSIVE'
JOIN performance_schema.threads c ON a.OWNER_THREAD_ID = c.THREAD_ID
JOIN performance_schema.threads d ON b.OWNER_THREAD_ID = d.THREAD_ID\G *************************** 1. row ***************************
locked_schema: slowtech
locked_table: t1
locked_type: Metadata Lock
waiting_processlist_id: 4
waiting_age: 259
waiting_query: alter table slowtech.t1 add c1 int
waiting_state: Waiting for table metadata lock
blocking_processlist_id: 2
blocking_age: 301
blocking_query: NULL
sql_kill_blocking_connection: KILL 2
1 row in set (0.00 sec)

输出一目了然,DDL操作如果要获得MDL,执行kill 2即可。

官方的sys.schematablelock_waits

实际上,MySQL 5.7在sys库中也集成了类似功能,同样的场景,其输出如下,

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

具体分析下官方的输出,

只有一个alter table操作,却产生了两条记录,而且两条记录的kill对象竟然还不一样,对表结构不熟悉及不仔细看记录内容的话,难免会kill错对象。

不仅如此,如果有N个查询被DDL操作堵塞,则会产生N*2条记录。在阻塞操作较多的情况下,这N*2条记录完全是个噪音。

而之前的SQL,无论有多少操作被阻塞,一个alter table操作,就只会输出一条记录。

如何查看阻塞会话已经执行过的操作

但上面这个SQL也有遗憾,其blocking_query为NULL,而在会话1中,其明明已经执行了三个SQL。

这个与performance_schema.threads(类似于show processlist)有关,其只会输出当前正在运行的SQL,对于已经执行过的,实际上是没办法看到。

但在线上,kill是一个需要谨慎的操作,毕竟你很难知道kill的是不是业务关键操作?又或者,是个批量update操作?那么,有没有办法抓到该事务之前的操作呢?

答案,有。

即Performance Schema中记录Statement Event(操作事件)的表,具体包括events_statements_current,events_statements_history,events_statements_history_long,prepared_statements_instances。

常用的是前面三个。

三者的表结构完全一致,其中,events_statements_history又包含了events_statements_current的操作,所以我们这里会使用events_statements_history。

终极SQL如下,

SELECT
locked_schema,
locked_table,
locked_type,
waiting_processlist_id,
waiting_age,
waiting_query,
waiting_state,
blocking_processlist_id,
blocking_age,
substring_index(sql_text,"transaction_begin;" ,-1) AS blocking_query,
sql_kill_blocking_connection
FROM
(
SELECT
b.OWNER_THREAD_ID AS granted_thread_id,
a.OBJECT_SCHEMA AS locked_schema,
a.OBJECT_NAME AS locked_table,
"Metadata Lock" AS locked_type,
c.PROCESSLIST_ID AS waiting_processlist_id,
c.PROCESSLIST_TIME AS waiting_age,
c.PROCESSLIST_INFO AS waiting_query,
c.PROCESSLIST_STATE AS waiting_state,
d.PROCESSLIST_ID AS blocking_processlist_id,
d.PROCESSLIST_TIME AS blocking_age,
d.PROCESSLIST_INFO AS blocking_query,
concat('KILL ', d.PROCESSLIST_ID) AS sql_kill_blocking_connection
FROM
performance_schema.metadata_locks a
JOIN performance_schema.metadata_locks b ON a.OBJECT_SCHEMA = b.OBJECT_SCHEMA
AND a.OBJECT_NAME = b.OBJECT_NAME
AND a.lock_status = 'PENDING'
AND b.lock_status = 'GRANTED'
AND a.OWNER_THREAD_ID <> b.OWNER_THREAD_ID
AND a.lock_type = 'EXCLUSIVE'
JOIN performance_schema.threads c ON a.OWNER_THREAD_ID = c.THREAD_ID
JOIN performance_schema.threads d ON b.OWNER_THREAD_ID = d.THREAD_ID
) t1,
(
SELECT
thread_id,
group_concat( CASE WHEN EVENT_NAME = 'statement/sql/begin' THEN "transaction_begin" ELSE sql_text END ORDER BY event_id SEPARATOR ";" ) AS sql_text
FROM
performance_schema.events_statements_history
GROUP BY thread_id
) t2
WHERE
t1.granted_thread_id = t2.thread_id \G *************************** 1. row ***************************
locked_schema: slowtech
locked_table: t1
locked_type: Metadata Lock
waiting_processlist_id: 4
waiting_age: 294
waiting_query: alter table slowtech.t1 add c1 int
waiting_state: Waiting for table metadata lock
blocking_processlist_id: 2
blocking_age: 336
blocking_query: delete from slowtech.t1 where id=2;select * from slowtech.t1;update slowtech.t1 set name='c' where id=1
sql_kill_blocking_connection: KILL 2
1 row in set, 1 warning (0.00 sec)

从上面的输出可以看到,blocking_query中包含了会话1中当前事务的所有操作,按执行的先后顺序输出。

需要注意的是,默认情况下,events_statements_history只会保留每个线程最近的10个操作,如果事务中进行的操作较多,实际上也是没办法抓全的。

Anyway, it is better than nothing!

MySQL 5.7中如何定位DDL被阻塞的问题的更多相关文章

  1. MySQL 5.6中如何定位DDL被阻塞的问题

    在上一篇文章<MySQL 5.7中如何定位DDL被阻塞的问题>中,对于DDL被阻塞问题的定位,我们主要是基于MySQL 5.7新引入的performance_schema.metadata ...

  2. MySQL 中如何定位 DDL 被阻塞的问题

    经常碰到开发.测试童鞋会问,线下开发.测试环境,执行了一个DDL,发现很久都没有执行完,是不是被阻塞了?要怎么解决? 包括在群里,也经常会碰到类似问题:DDL 被阻塞了,如何找到阻塞它的 SQL ? ...

  3. 【mysql】数据库中的DML DDL DCL TCL 及 Online DDL

    DDL(data definition language) : 数据库定义语言 用来定义创建操作表的时候用到的一些sql命令,比如CREATE.ALTER.DROP等等. DML(data manip ...

  4. 物联网应用中实时定位与轨迹回放的解决方案 – Redis的典型运用(转载)

    物联网应用中实时定位与轨迹回放的解决方案 – Redis的典型运用(转载)   2015年11月14日|    by: nbboy|    Category: 系统设计, 缓存设计, 高性能系统 摘要 ...

  5. mysql init-file参数中语句限制

    mysql 启动选项中的init-file文件的内容目测只能是dml语句,不能包含ddl,否则执行就会报错,但不影响启动本身..太扯了..

  6. MySQL InnoDB 修改表列Online DDL

    概述 一般来说数据库结构一经设计,不能轻易更改,因为更改DDL(Data Definition Language)操作代价很高,所以在进行数据库结构设计时需要谨慎. 但是业务发展是未知的,特别是那些变 ...

  7. MySQL 并行复制演进及 MySQL 8.0 中基于 WriteSet 的优化

    MySQL 8.0 可以说是MySQL发展历史上里程碑式的一个版本,包括了多个重大更新,目前 Generally Available 版本已经已经发布,正式版本即将发布,在此将介绍8.0版本中引入的一 ...

  8. mysql删除表中的记录

    大家都知道,在MySQL中删除一个表中的记录有两种方法,一种是DELETE FROM TABLENAME WHERE... , 还有一种是TRUNCATE TABLE TABLENAME. DELET ...

  9. MySQL 5.6 中一个重要的优化——Index Condition Pushdown,究竟push down了什么

    1        问题描述 一条SQL,在数据库中是如何执行的呢?相信很多人都会对这个问题比较感兴趣.当然,要完整描述一条SQL在数据库中的生命周期,这是一个非常巨大的问题,涵盖了SQL的词法解析.语 ...

随机推荐

  1. 章节三、5-Getters-Setters和this关键字part01

    我们新建第一个Car类: package introduction; public class Car { //颜色 private String color; //生成商 private Strin ...

  2. IPD咨询如何才能真正落地?

    文/资深顾问 杨学明 IPD作为先进的产品开发理念,思想起源于PRTM公司,PACE,培思的力量,首先在IBM和波音公司迅速完善,中国是深圳华为公司. 1992年IBM公司利润停止增长,财务困难,IB ...

  3. UE4照片级渲染Demo

  4. Linux中对swap分区的配置

    swap分区的安装与正常分区的安装大致相同,我这里就只说一下不同 大家可先看我上一篇的安装:https://www.cnblogs.com/feiquan/p/9219447.html 1.查看swa ...

  5. C# 反射给对象赋值遇到的问题——类型转换

    反射给对象赋值遇到的问题——类型转换 给一个对象属性赋值可以通过PropertyInfo.SetValue()方式进行赋值,但要注意值的类型要与属性保持一致.    创建对象实例的两种方法: 1. 1 ...

  6. 原生js :removeClass和addClass

    function removeClass(obj, aClass) { var re = new RegExp('\\b' + aClass + '\\b'); if (obj.className ! ...

  7. c/c++ 智能指针 shared_ptr 和 new结合使用

    智能指针 shared_ptr 和 new结合使用 用make_shared函数初始化shared_ptr是最推荐的,但有的时候还是需要用new关键字来初始化shared_ptr. 一,先来个表格,唠 ...

  8. Lua不显示小数点0的部分

    我的环境:Unity3D 5.3.7p4 XLua版本v2.1.6 基于Lua5.3 (https://github.com/Tencent/xLua) 在Lua中数字不区分整型或浮点型,所有都是nu ...

  9. 监控MySQL或Web服务是否正常

    在工作中,我们往往利用脚本定时监控本地.远端数据库服务端或Web服务是否运行正常,例如:负载高.cup高.连接数满了等.... 方法一:根据端口 本地:netstat/ss/lsof ①   nets ...

  10. Linux 小知识翻译 - 「BitTorrent」

    这次聊聊「BitTorrent」. (也就是下片子的 BT) BitTorrent是文件传输的一种.它可以在服务端负担很小的情况下高速的传输文件. BitTorrent最大的特点就是服务端可以将文件的 ...