yh86银河国际(中國)官方网站-2024 European Cup





一场MySQL数据库里的“交通堵塞”:MDL LOCK堵塞揭秘

日期:2024-02-27

今天我们要给大家揭秘一个MySQL数据库里可能发生的“交通堵塞”现象——MDL LOCK堵塞。我们来一探究竟!

1案发现场LOCK TABLE READ VS INSERT INTO

 

案例大概的截图如下:

(点击可放大查看)

这里是测试出来的,线上当时也是一个lock table read的语句和一大批insert into的语句被堵塞,当然要恢复很简单,我们可以通过2种方式,来查杀堵塞源头,如下:

  • table 级别的MDL LOCK,除非手动发送lock table,那么其持续时间通常为事务级别,而innodb_trx中记录了全部的事务(只读和读写),因此我们可以通过innodb_trx查询事务持续时间长于processlist中“Waiting for table metadata lock”最长时间的session的事务通常就是堵塞源头(当然也有例外,这个以后再讨论)。

     

  • 访问sys.schema_table_lock_waits进行判断,如果为5.7需要手动开启MDL LOCK的instrument,并且需要注意本视图只能检查table级别的MDL LOCK,也就是本例中的“Waiting for table metadata lock”。

2测试堵塞:谁动了我的INSERT?

但是在测试中,我们发现如下的执行顺序insert是可以执行,

表结构和数据
CREATE TABLE `t1` (
  `id` int(11) NOT NULL,
  `a` int(11) DEFAULT NULL,
  `b` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `a` (`a`)
)
mysql> select * from t1;
+----+------+------+
| id | a    | b    |
+----+------+------+
|  1 |    1 |    1 |
|  3 |    3 |    3 |
|  5 |    5 |    5 |
+----+------+------+
 
2.1 模拟(S1事务不提交)

 

 

查看session状态如下:

mysql> show processlist;
+----+-----------------+-----------+------+---------+------+---------------------------------+--------------------+-----------+---------------+
| Id | User            | Host      | db   | Command | Time | State                           | Info               | Rows_sent | Rows_examined |
+----+-----------------+-----------+------+---------+------+---------------------------------+--------------------+-----------+---------------+
|  1 | event_scheduler | localhost | NULL | Daemon  | 1046 | Waiting on empty queue          | NULL               |         0 |             0 |
|  3 | root            | localhost | new  | Query   |    0 | starting                        | show processlist   |         0 |             0 |
|  4 | root            | localhost | new  | Query   |  392 | Waiting for table metadata lock | lock table t1 read |         0 |             0 |
|  5 | root            | localhost | new  | Sleep   |  341 |                                 | NULL               |         0 |             0 |
+----+-----------------+-----------+------+---------+------+---------------------------------+--------------------+-----------+---------------+

(左右滑动查看更多)

这里可以看到这里只有lock table read的S2处于堵塞状态,而S3的insert的语句并没有堵塞,那么案例中的insert堵塞语句是哪里来的呢?

 

2.2 模拟(S1提交)

 

查看session状态如下:

+----+-----------------+-----------+------+---------+------+---------------------------------+-------------------------------+-----------+---------------+
| Id | User            | Host      | db   | Command | Time | State                           | Info                          | Rows_sent | Rows_examined |
+----+-----------------+-----------+------+---------+------+---------------------------------+-------------------------------+-----------+---------------+
|  1 | event_scheduler | localhost | NULL | Daemon  | 1325 | Waiting on empty queue          | NULL                          |         0 |             0 |
|  3 | root            | localhost | new  | Query   |    0 | starting                        | show processlist              |         0 |             0 |
|  4 | root            | localhost | new  | Sleep   |  671 |                                 | NULL                          |         0 |             0 |
|  5 | root            | localhost | new  | Query   |    4 | Waiting for table metadata lock | insert into t1 values( 9,9,9) |         0 |             0 |
+----+-----------------+-----------+------+---------+------+---------------------------------+-------------------------------+-----------+---------------

(左右滑动查看更多)

这个时候因为lock table read执行成功了,insert语句继续插入行则被MDL LOCK堵塞了。

 

 

3问题汇总和分析

 

  • 问题1:为什么模拟中lock table table read堵塞后,insert可以执行?

  • 问题2:为什么模拟中lock table table read执行成功后,insert会被堵塞?

  • 问题3:为什么案例中lock table table read被堵塞后,insert也被堵塞?

 

我们来一个问题一个问题的讲述。首先我们要知道MDL LOCK有2个矩阵,一个为优先级矩阵,一个为兼容矩阵,当判断是否能过获取的MDL LOCK的时候需要调用MDL_lock::can_grant_lock函数进行判断,其判断的主要逻辑就是,

if (!(m_waiting.bitmap() & waiting_incompat_map)) 
  {
    if (! (fast_path_granted_bitmap() & granted_incompat_map)) //unobtrusive类型的MDL LOCK
    {
      if (! (m_granted.bitmap() & granted_incompat_map))

(左右滑动查看更多)

首先想看优先级矩阵,然后再看兼容矩阵,其中优先级矩阵为:

 Request  |         Pending requests for lock          |
        type    | S  SH  SR  SW  SWLP  SU  SRO  SNW  SNRW  X |
      ----------+--------------------------------------------+
      S         | +   +   +   +    +    +   +    +     +   - |
      SH        | +   +   +   +    +    +   +    +     +   + |
      SR        | +   +   +   +    +    +   +    +     -   - |
      SW        | +   +   +   +    +    +   +    -     -   - |
      SWLP      | +   +   +   +    +    +   -    -     -   - |
      SU        | +   +   +   +    +    +   +    +     +   - |
      SRO       | +   +   +   -    +    +   +    +     -   - |
      SNW       | +   +   +   +    +    +   +    +     +   - |
      SNRW      | +   +   +   +    +    +   +    +     +   - |
      X         | +   +   +   +    +    +   +    +     +   + |

(左右滑动查看更多)

兼容矩阵为:

Request  |  Granted requests for lock            |
        type    | S  SH  SR  SW  SWLP  SU  SRO  SNW  SNRW  X  |
      ----------+---------------------------------------------+
      S         | +   +   +   +    +    +   +    +    +    -  |
      SH        | +   +   +   +    +    +   +    +    +    -  |
      SR        | +   +   +   +    +    +   +    +    -    -  |
      SW        | +   +   +   +    +    +   -    -    -    -  |
      SWLP      | +   +   +   +    +    +   -    -    -    -  |
      SU        | +   +   +   +    +    -   +    -    -    -  |
      SRO       | +   +   +   -    -    +   +    +    -    -  |
      SNW       | +   +   +   -    -    -   +    -    -    -  |
      SNRW      | +   +   -   -    -    -   -    -    -    -  |
      X         | -   -   -   -    -    -   -    -    -    -  |

(左右滑动查看更多)

当然期间有unobtrusive类型的MDL LOCK,这部分主要是优化MDL LOCK系统性能的,并不改变优先级和兼容性。

 

3.1 问题1

这个问题我们按照时间序列进行描述,

 

实际上这里s3的insert因为优先级矩阵并不会被堵塞中的MDL_SHARED_READ_ONLY(SRO)堵塞如下:

Request  |         Pending requests for lock          |
        type    | S  SH  SR  SW  SWLP  SU  SRO  SNW  SNRW  X |
      ----------+--------------------------------------------+
      SW        | +   +   +   +    +    +   +    -     -   - |

(左右滑动查看更多)

而比对兼容矩阵的时候同样MDL_SHARED_WRITE(SW)和MDL_SHARED_WRITE(SW)是兼容的因此就执行成功了。

Request  |  Granted requests for lock            |
        type    | S  SH  SR  SW  SWLP  SU  SRO  SNW  SNRW  X  |
      ----------+---------------------------------------------+
      SW        | +   +   +   +    +    +   -    -    -    -  |

(左右滑动查看更多)

 

3.2 问题2

有了问题1的基础,问题2我们可以直接看兼容矩阵,因为S1事务提交了,S2的lock table table read执行成功了,这个S3插入数据,实际上就是看MDL_SHARED_READ_ONLY(SRO)是否和MDL_SHARED_WRITE(SW)兼容,如下:

Request  |  Granted requests for lock            |
        type    | S  SH  SR  SW  SWLP  SU  SRO  SNW  SNRW  X  |
      ----------+---------------------------------------------+
      SW        | +   +   +   +    +    +   -    -    -    -  |

(左右滑动查看更多)

可以看到并不兼容,因此堵塞。

 

3.3 问题3

这个问题实际上和lock table read一大批表有关,因为加MDL LOCK锁并不是一气呵成的。比如lock t1 read,t2 read,t3 read,t4 read,其中t4有一个for update事务,这个时候t1\t2\t3的lock table table read就可以能执行成功,而整个语句堵塞在t4的mdl lock上,而其他session如果对t1,t2,t3进行insert 则也是会堵塞的。测试如下:

 

这通常和mysqldump分库导出表没有去掉lock-tables有关,这会导致一个库的所有表现执行lock table read操作,因此我们要用--single-transaction来取掉这个加锁的操作,

Option automatically turns off --lock-tables

(左右滑动查看更多)

这也是实际案例中的遇到的问题。

 

 

4总结

 

 

本案例中我们得到几个结论:

  • 语句是否能够执行主要看的优先级矩阵和兼容矩阵,前者用于判断本次执行的语句和堵塞中的MDL LOCK谁的优先级更高,优先级更高则可以继续判断兼容矩阵。后者用于判定本次执行的语句和获取MDL LOCK的语句(或者事务)是否兼容。

  • lock table read一大批表的时候,可能某些表加锁成功了,而某些表加锁堵塞了,看起来是整个lock table read语句堵塞了。

  • mysqldump导出如果全是innodb表肯定是要--single-transaction的。

 

最后,希望通过这次“交通堵塞”案件的揭秘,和大家共同深入了解MySQL中MDL LOCK的工作原理和堵塞问题。在未来的工作中,更加注意数据库操作的规范性,避免类似问题的发生。

 

CREATE TABLE `t1` (
  `id` int(11) NOT NULL,
  `a` int(11) DEFAULT NULL,
  `b` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `a` (`a`)
)
mysql> select * from t1;
+----+------+------+
| id | a    | b    |
+----+------+------+
|  1 |    1 |    1 |
|  3 |    3 |    3 |
|  5 |    5 |    5 |
+----+------+------+
 

锻造凝炼IT服务 助推用户事业发展
地址:北京市西城区百万庄大街11号粮科大厦3层
电话:(010)58523737
传真:(010)58523739