一、元数据锁等待

1、元数据锁的定义

 MDL 全称为 metadata lock,即元数据锁,一般也可称为字典锁。MDL 的主要作用是为了管理数据库对象的并发访问和确保元数据一致性。元数据锁适用对象包含:table、schema、procedures, functions, triggers, scheduled events、tablespaces 。

 

有以下特点:

  • 元数据锁是server层的锁,表级锁,每执行一条DML、DDL语句时都会申请MDL锁。
  • DML操作需要metadata读锁,DDL操作需要metadata写锁,加锁过程是系统自动控制,无法直接干预。
  • 读锁和写锁之间相互阻塞,写锁和写锁之间互相阻塞,读锁和读锁之间不会产生阻塞(因为DML之间的阻塞是通过innodb引擎的行锁来进行的)。
  • DDL 的写锁请求优先级高于 DML的读锁。
  • MDL锁保护的是事务级别的,只有等到事务结束后才会释放。
  • 同时操作多个表时,按出现的先后顺序逐个表去获取元数据锁。
  • 单个DML语句,在语句执行完后会立即释放元数据锁。
  • DDL 语句、LOCK TABLES 和其他类似语句按名称顺序获取锁,对于隐式使用的表(例如外键关系中也必须锁定的表)可能会以不同的顺序获取锁。

 

 加入元数据锁的目的:

  • 事务隔离问题:比如在RR隔离级别下,会话A在2次查询期间,会话B对表结构做了修改,两次查询结果就会不一致,无法满足可重复读的要求。
  • 数据复制问题:比如会话A执行了多条更新语句期间,会话B做了表结构变更并且先提交,就会导致slave在重做时,先重做alter,再重做update,这样会导致复制出现错误。

 

2、快速查看是否有元数据锁等待产生

show processlist或select * from information_schema.processlist,查看结果集中STATE列是否有Waiting for table metadata lock显示,有则为产生了元数据锁等等。

[5.7.37-log]>select * from information_schema.processlist;
+----+------+-----------+--------------------+---------+------+---------------------------------+----------------------------------------------+
| ID | USER | HOST      | DB                 | COMMAND | TIME | STATE                           | INFO                                         |
+----+------+-----------+--------------------+---------+------+---------------------------------+----------------------------------------------+
| 10 | root | localhost | information_schema | Query   |    0 | executing                       | select * from information_schema.processlist |
|  5 | root | localhost | performance_schema | Sleep   |  164 |                                 | NULL                                         |
|  9 | root | localhost | test               | Query   |   22 | Waiting for table metadata lock | alter table t1 add addr varchar(100)         |
|  8 | root | localhost | test               | Sleep   |   24 |                                 | NULL                                         |
+----+------+-----------+--------------------+---------+------+---------------------------------+----------------------------------------------+
4 rows in set (0.00 sec)

 

3、开启并监控元数据锁信息

1)永久生效,在配置文件的[mysqld]节点下,加入开启参数

[mysqld]
performance_schema_instrument='wait/lock/metadata/sql/mdl=ON'

 

2) 临时生效,数据库重启后,变回默认值

update performance_schema.setup_instruments set ENABLED='Yes',TIMED='YES' where name='wait/lock/metadata/sql/mdl';

 

3)监控元数据锁信息:都记录在performance_schema.metadata_locks表中

[5.7.37-log]>select * from performance_schema.metadata_locks;
+-------------+--------------------+----------------+-----------------------+-------------+---------------+-------------+--------+-----------------+----------------+
| OBJECT_TYPE | OBJECT_SCHEMA      | OBJECT_NAME    | OBJECT_INSTANCE_BEGIN | LOCK_TYPE   | LOCK_DURATION | LOCK_STATUS | SOURCE | OWNER_THREAD_ID | OWNER_EVENT_ID |
+-------------+--------------------+----------------+-----------------------+-------------+---------------+-------------+--------+-----------------+----------------+
| TABLE       | performance_schema | metadata_locks |       140398722883648 | SHARED_READ | TRANSACTION   | GRANTED     |        |              28 |              8 |
+-------------+--------------------+----------------+-----------------------+-------------+---------------+-------------+--------+-----------------+----------------+
1 row in set (0.00 sec)

 

4、模拟元数据锁等待

 

--session 1
[5.7.37-log]>begin;
Query OK, 0 rows affected (0.00 sec)

[5.7.37-log]>select * from t1;
+------+------+
| id   | name |
+------+------+
|    1 | NULL |
|    4 | NULL |
|    5 | NULL |
+------+------+
3 rows in set (0.00 sec)

--session 2
[5.7.37-log]>alter table t1 add addr varchar(100);


--session 3 # 查看元数据锁等待
select a.object_schema locked_schema,
    a.object_name locked_table,
    "metadata lock" locked_type,
    c.processlist_id waiting_processlist_id,
    c.processlist_time waiting_time,
    c.processlist_info waiting_query,
    c.processlist_state waiting_state,
    d.processlist_id blocking_processlist_id,
    d.processlist_time blocking_time,
    e.sql_text blocking_query,
    concat('kill ',d.processlist_id, ';') 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
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
join performance_schema.events_statements_current e
    on b.owner_thread_id = e.thread_id\G

*************************** 1. row ***************************
               locked_schema: test
                locked_table: t1
                 locked_type: metadata lock
      waiting_processlist_id: 5
                waiting_time: 13
               waiting_query: alter table t1 add addr varchar(100)
               waiting_state: Waiting for table metadata lock
     blocking_processlist_id: 4
               blocking_time: 42
              blocking_query: select * from t1
sql_kill_blocking_connection: kill 4
1 row in set (0.00 sec)

 

二、行锁等待

有锁阻塞时,可以通过show engine innodb status命令,及表information_schema.innodb_lock_waits进行查看堵塞信息,也可以通过innodb_lock_waits表结合其他表,查看阻塞与被阻塞的进程信息
show engine innodb status:

[5.7.37-log]>show engine innodb status\G
------------
TRANSACTIONS
------------
Trx id counter 641553
Purge done for trx's n:o < 641542 undo n:o < 0 state: running but idle
History list length 0
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 421875474013808, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 641552, ACTIVE 12 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 5, OS thread handle 140399866201856, query id 75 localhost root updating
update t1 set name='abcd' where id=1
------- TRX HAS BEEN WAITING 12 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 148 page no 3 n bits 72 index GEN_CLUST_INDEX of table `test`.`t1` trx id 641552 lock_mode X waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
 0: len 6; hex 000000000500; asc       ;;
 1: len 6; hex 00000009b52b; asc      +;;
 2: len 7; hex c7000002530110; asc     S  ;;
 3: len 4; hex 80000001; asc     ;;
 4: SQL NULL;

------------------
---TRANSACTION 641542, ACTIVE 1491 sec
2 lock struct(s), heap size 1136, 4 row lock(s)
MySQL thread id 4, OS thread handle 140399866472192, query id 30 localhost root

 

information_schema.innodb_lock_waits:

[5.7.37-log]>select * from information_schema.innodb_lock_waits;
+-------------------+-------------------+-----------------+------------------+
| requesting_trx_id | requested_lock_id | blocking_trx_id | blocking_lock_id |
+-------------------+-------------------+-----------------+------------------+
| 641553            | 641553:148:3:3    | 641542          | 641542:148:3:3   |
+-------------------+-------------------+-----------------+------------------+
1 row in set, 1 warning (0.00 sec)

 

查看阻塞信息:

select 
    wt.thread_id waiting_thread_id,
    wt.processlist_id waiting_processlist_id,
    wt.processlist_time waiting_time,
    wt.processlist_info waiting_query,
    bt.thread_id blocking_thread_id,
    bt.processlist_id blocking_processlist_id,
    bt.processlist_time blocking_time,
    c.sql_text blocking_query,
    concat('kill ',bt.processlist_id, ';') sql_kill_blocking_connection
from information_schema.innodb_lock_waits l join information_schema.innodb_trx b
    on b.trx_id = l.blocking_trx_id
join information_schema.innodb_trx w
    on w.trx_id = l.requesting_trx_id
join performance_schema.threads wt
    on w.trx_mysql_thread_id=wt.processlist_id
join performance_schema.threads bt
    on b.trx_mysql_thread_id=bt.processlist_id
join performance_schema.events_statements_current c
    on bt.thread_id=c.thread_id\G

*************************** 1. row ***************************
           waiting_thread_id: 30
      waiting_processlist_id: 5
                waiting_time: 27
               waiting_query: update t1 set name='abcd' where id=1
          blocking_thread_id: 29
     blocking_processlist_id: 4
               blocking_time: 1681
              blocking_query: select * from t1 for update
sql_kill_blocking_connection: kill 4;
1 row in set, 1 warning (0.00 sec)

 

查看引起堵塞的事务SQL

select
    wt.thread_id waiting_thread_id,
    wt.processlist_id waiting_processlist_id,
    wt.processlist_time waiting_time,
    wt.processlist_info waiting_query,
    bt.thread_id blocking_thread_id,
    bt.processlist_id blocking_processlist_id,
    bt.processlist_time blocking_time,
    c.sql_text blocking_query,
    concat('kill ', bt.processlist_id, ';') sql_kill_blocking_connection
from
    information_schema.innodb_lock_waits l
    join information_schema.innodb_trx b on b.trx_id = l.blocking_trx_id
    join information_schema.innodb_trx w on w.trx_id = l.requesting_trx_id
    join performance_schema.threads wt on w.trx_mysql_thread_id = wt.processlist_id
    join performance_schema.threads bt on b.trx_mysql_thread_id = bt.processlist_id
    join (
        select a.thread_id,group_concat(a.sql_text order by a.event_id SEPARATOR ';\n') sql_text
        from
            (
                select thread_id,event_id,event_name,sql_text
                from performance_schema.events_statements_current
                union
                select thread_id,event_id,event_name,sql_text
                from performance_schema.events_statements_history
            ) a
            join (
                select
                    thread_id,
                    max(event_id) max_event_id,
                    max(case when event_name = 'statement/sql/begin' then event_id else 0 end) max_begin_id,
                    max(case when event_name = 'statement/sql/rollback' then event_id else 0 end) max_rollback_id,
                    max(case when event_name = 'statement/sql/commit' then event_id else 0 end) max_commit_id
                from
                    (
                        select thread_id,event_id,event_name,sql_text
                        from performance_schema.events_statements_current
                        union
                        select thread_id,event_id,event_name,sql_text
                        from performance_schema.events_statements_history
                    ) tmp
                group by thread_id
            ) b on a.thread_id = b.thread_id
        where
            a.event_id = b.max_event_id
            or (
                a.event_id >= max_begin_id
                and max_begin_id > greatest(max_rollback_id, max_commit_id)
            )
        group by a.thread_id
    ) c on bt.thread_id = c.thread_id
\G

 

 

三、未提交事务

select 
    a.trx_id,
    a.trx_state,
    a.trx_started,
    b.processlist_time,
    b.thread_id,
    b.processlist_id,
    b.processlist_user,
    b.processlist_host,
    b.processlist_db,
    c.sql_text,
    concat('kill ',b.processlist_id, ';') sql_kill_connection
from information_schema.innodb_trx a join performance_schema.threads b
    on a.trx_mysql_thread_id=b.processlist_id
join performance_schema.events_statements_current c
    on b.thread_id=c.thread_id\G


*************************** 1. row ***************************
             trx_id: 641560
          trx_state: RUNNING
        trx_started: 2022-10-30 10:49:22
   processlist_time: 74
          thread_id: 29
     processlist_id: 4
   processlist_user: root
   processlist_host: localhost
     processlist_db: test
           sql_text: select * from t1 for update
sql_kill_connection: kill 4;
*************************** 2. row ***************************
             trx_id: 641561
          trx_state: LOCK WAIT
        trx_started: 2022-10-30 10:50:28
   processlist_time: 8
          thread_id: 30
     processlist_id: 5
   processlist_user: root
   processlist_host: localhost
     processlist_db: test
           sql_text: update t1 set name='abcd' where id=1
sql_kill_connection: kill 5;
2 rows in set (0.00 sec)

 

查看完整未提交的事务SQL

select 
    a.trx_id,
    a.trx_state,
    a.trx_started,
    b.processlist_time,
    b.thread_id,
    b.processlist_id,
    b.processlist_user,
    b.processlist_host,
    b.processlist_db,
    c.sql_text,
    concat('kill ',b.processlist_id, ';') sql_kill_connection
from information_schema.innodb_trx a join performance_schema.threads b
    on a.trx_mysql_thread_id=b.processlist_id
    join (
        select a.thread_id,group_concat(a.sql_text order by a.event_id SEPARATOR ';\n') sql_text
        from (
                select thread_id,event_id,event_name,sql_text
                from performance_schema.events_statements_current
                union
                select thread_id,event_id,event_name,sql_text
                from performance_schema.events_statements_history
            ) a
            join (
                select thread_id,
                    max(event_id) max_event_id,
                    max(case when event_name = 'statement/sql/begin' then event_id else 0 end) max_begin_id,
                    max(case when event_name = 'statement/sql/rollback' then event_id else 0 end) max_rollback_id,
                    max(case when event_name = 'statement/sql/commit' then event_id else 0 end) max_commit_id
                from (
                        select thread_id,event_id,event_name,sql_text
                        from performance_schema.events_statements_current
                        union
                        select thread_id,event_id,event_name,sql_text
                        from performance_schema.events_statements_history
                    ) tmp
                group by thread_id
            ) b on a.thread_id = b.thread_id
        where a.event_id = b.max_event_id
            or (
                a.event_id >= max_begin_id
                and max_begin_id > greatest(max_rollback_id, max_commit_id)
            )
        group by a.thread_id
    ) c
    on b.thread_id=c.thread_id
\G