MDL(Meta Data LocK)的作用

在MySQL5.1及之前的版本中,如果有未提交的事务trx,当执行DROP/RENAME/ALTER TABLE RENAME操作时,不会被其他事务阻塞住。这会导致如下问题(MySQL bug#989)

master: 未提交的事务,但SQL已经完成(binlog也准备好了),表schema发生更改,在commit的时候不会被察觉到.

slave: 在binlog里是以事务提交顺序记录的,DDL隐式提交,因此在备库先执行DDL,后执行事务trx,由于trx作用的表已经发生了改变,因此trx会执行失败。 在DDL时的主库DML压力越大,这个问题触发的可能性就越高

在5.5引入了MDL(meta data lock)锁来解决在这个问题

MDL锁的类型

metadata lock也是一种锁。每个metadata lock都会定义锁住的对象,锁的持有时间和锁的类型

属性范围作用
GLOBAL全局锁主要作用是防止DDL和写操作的过程中执行 set golbal_read_only =on 或flush tables with read lock;
commit提交保护锁主要作用是执行flush tables with read lock后,防止已经开始在执行的写事务提交
SCHEMA库锁对象
TABLE表锁对象
FUNCTION函数锁对象
PROCEDURE存储过程锁对象
TRIGGER触发器锁对象
EVENT事件锁对象

这些锁具有以下层级关系 MDL_SCOPE.png

MDL锁的简单示例

在实际工作中,最常见的MDL冲突就DDL的操作被没用提交的事务所阻塞。 我们下面通过一个具体的实例来演示DDL加MDL锁的过程。在这个实例中,利用gdb来跟踪DDL申请MDL锁的过程。

会话1:

  1. mysql> create table ti(id int primary key, c1 int, key(c1)) engine=InnoDB
  2. stats_auto_recalc=default;
  3. Query OK, 0 rows affected (0.03 sec)
  4. mysql> insert into ti values (1,1), (2,2);
  5. Query OK, 2 rows affected (0.03 sec)
  6. Records: 2 Duplicates: 0 Warnings: 0
  7. mysql> start transaction;
  8. Query OK, 0 rows affected (0.00 sec)
  9. mysql> select * from ti;
  10. +----+------+
  11. | id | c1 |
  12. +----+------+
  13. | 1 | 1 |
  14. | 2 | 2 |
  15. +----+------+
  16. 2 rows in set (0.00 sec)

再开启第二个会话,利用gdb来跟踪mysql加MDL的过程 会话2:

  1. [root@localhost mysql]# ps -ef|grep mysql
  2. root 3336 2390 0 06:33 pts/2 00:00:01 /u02/mysql/bin/mysqld --basedir=/u02/mysql/ --datadir=/u02/mysql/data
  3. --plugin-dir=/u02/mysql//lib/plugin --user=root
  4. --log-error=/u02/mysql/tmp/error1.log --open-files-limit=10240
  5. --pid-file=/u02/mysql/tmp/mysql.pid
  6. --socket=/u02/mysql/tmp/mysql.sock --port=3306
  7. [root@localhost mysql]# gdb -p 3336
  8. ----在GDB设置以下断点
  9. (gdb) b MDL_context::acquire_lock
  10. Breakpoint 1 at 0x730cab: file /u02/mysql-server-5.6/sql/mdl.cc, line 2187.
  11. (gdb) b lock_rec_lock
  12. Breakpoint 2 at 0xb5ef50: file /u02/mysql-server-5.6/storage/innobase/lock/lock0lock.cc, line 2296.
  13. (gdb) c
  14. Continuing.....

开启第三个会话

  1. mysql> alter table ti stats_auto_recalc=1;
  2. 这个操作被hang

在会话2中执行下面的操作

  1. (gdb) p mdl_request
  2. $1 = (MDL_request *) 0x7f697d1c3bd0
  3. (gdb) p *mdl_request
  4. $2 = {
  5. type = MDL_INTENTION_EXCLUSIVE, duration = MDL_STATEMENT, next_in_list = 0x7f697002a560, prev_in_list = 0x7f697d1c3df8, ticket = 0x0, key = {m_length = 3, m_db_name_length = 0,
  6. m_ptr = '\000' <repeats 20 times>, "0|\002p\000\000\001\000\060<\034}i\177\000\000>\240\344\000\000\000\000\000\000\t\000pi\177\000\000\000\t\000pi\177\000\000`>\034}i\177\000\000V\312\344\000\000\000\000\000\240>\034}i\177\000\000\333\361\254\000b\001\000\000\a?\000\001", '\000' <repeats 20 times>, "0|\002p\000\000\001\000\220<\034}i\177\000\000>\240\344\000\000\000\000\000\340\236\002pi\177\000\000\333\361\254\000\000\000\000\000\a?\000\001", '\000' <repeats 12 times>"\340, >\034}i\177\000\000\060|\002p\000\000\001\000\350\062\220\003\000\000\000\000\333\361\254\000\000\000\000\000$\226\363", '\000' <repeats 14 times>,
  7. "?\034}i\177\000\000\060|\002p\000\000\001\000\000=\034}i\177\000\000>\240\344\000\000\000\000\000\000"...,
  8. static m_namespace_to_wait_state_name = {
  9. {m_key = 101,
  10. m_name = 0xf125a2 "Waiting for global read lock", m_flags = 0},
  11. {m_key = 102,
  12. m_name = 0xf125c0 "Waiting for schema metadata lock", m_flags = 0},
  13. {m_key = 103,
  14. m_name = 0xf125e8 "Waiting for table metadata lock", m_flags = 0},
  15. {m_key = 104,
  16. m_name = 0xf12608 "Waiting for stored function metadata lock", m_flags = 0},
  17. {m_key = 105,
  18. m_name = 0xf12638 "Waiting for stored procedure metadata lock", m_flags = 0},
  19. {m_key = 106,
  20. m_name = 0xf12668 "Waiting for trigger metadata lock", m_flags = 0},
  21. {m_key = 107,
  22. m_name = 0xf12690 "Waiting for event metadata lock", m_flags = 0},
  23. {m_key = 108,
  24. m_name = 0xf126b0 "Waiting for commit lock", m_flags = 0}}}}
  25. (gdb)

从上面的输出中,我只能看到申请了一个语句级别的MDL_INTENTION_EXCLUSIVE。并没有看到什么其他有意义的信息。我们继续gdb跟踪

  1. (gdb) p *(mdl_request->next_in_list)
  2. $3 = {type = MDL_INTENTION_EXCLUSIVE, duration = MDL_TRANSACTION, next_in_list = 0x7f697002a388, prev_in_list = 0x7f697d1c3bd8, ticket = 0x0, key = {m_length = 7, m_db_name_length = 4,
  3. m_ptr = "\001test\000\000\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217\217",
  4. static m_namespace_to_wait_state_name = {
  5. {m_key = 101,
  6. m_name = 0xf125a2 "Waiting for global read lock", m_flags = 0},
  7. {m_key = 102,
  8. m_name = 0xf125c0 "Waiting for schema metadata lock", m_flags = 0},
  9. {m_key = 103,
  10. m_name = 0xf125e8 "Waiting for table metadata lock", m_flags = 0},
  11. {m_key = 104,
  12. m_name = 0xf12608 "Waiting for stored function metadata lock", m_flags = 0},
  13. {m_key = 105,
  14. m_name = 0xf12638 "Waiting for stored procedure metadata lock", m_flags = 0},
  15. {m_key = 106,
  16. m_name = 0xf12668 "Waiting for trigger metadata lock", m_flags = 0},
  17. {m_key = 107,
  18. m_name = 0xf12690 "Waiting for event metadata lock", m_flags = 0},
  19. {m_key = 108,
  20. m_name = 0xf126b0 "Waiting for commit lock", m_flags = 0}}}}

从上面的输出中,我们看到了需要在test(见输出中的 m_ptr = “\001test)数据库上加一把事务级的MDL_INTENTION_EXCLUSIVE锁。它并没有告诉我们最终的MDL会落在哪个对象上。我们继续跟踪

  1. $4 = {type = MDL_SHARED_UPGRADABLE, duration = MDL_TRANSACTION, next_in_list = 0x0, prev_in_list = 0x7f697002a568, ticket = 0x0, key = {m_length = 9, m_db_name_length = 4,
  2. m_ptr = "\002test\000ti", '\000' <repeats 378 times>,
  3. static m_namespace_to_wait_state_name = {
  4. {m_key = 101,
  5. m_name = 0xf125a2 "Waiting for global read lock", m_flags = 0},
  6. {m_key = 102,
  7. m_name = 0xf125c0 "Waiting for schema metadata lock", m_flags = 0},
  8. {m_key = 103,
  9. m_name = 0xf125e8 "Waiting for table metadata lock", m_flags = 0},
  10. {m_key = 104,
  11. m_name = 0xf12608 "Waiting for stored function metadata lock", m_flags = 0},
  12. {m_key = 105,
  13. m_name = 0xf12638 "Waiting for stored procedure metadata lock", m_flags = 0},
  14. {m_key = 106,
  15. m_name = 0xf12668 "Waiting for trigger metadata lock", m_flags = 0},
  16. {m_key = 107,
  17. m_name = 0xf12690 "Waiting for event metadata lock", m_flags = 0},
  18. {m_key = 108,
  19. m_name = 0xf126b0 "Waiting for commit lock", m_flags = 0}}}}

从上面的输出中,我们可以看出最终是要在test数据库的ti对象上加一把MDL_SHARED_UPGRADABLE锁。在做DDL时会先加MDL_SHARED_UPGRADABLE锁,然后升级到MDL_EXCLUSIVE锁

我来执行下面的过程 会话1

  1. mysql> commit;
  2. Query OK, 0 rows affected (5.51 sec)

会话2

  1. (gdb) p *mdl_request
  2. $5 = {type = MDL_EXCLUSIVE, duration = MDL_TRANSACTION, next_in_list = 0x20302000000, prev_in_list = 0x200000001, ticket = 0x0, key = {m_length = 9, m_db_name_length = 4,
  3. m_ptr = "\002test\000ti\000\000\000\000@\031\220\003\000\000\000\000\333\361\254\000\000\000\000\000\260<\034}i\177\000\000\302\362\254\000\000\000\000\000\300<\034}i\177\000\000\060|\002pi\177\000\000\320<\034}i\177\000\000\360\236\344\000\000\000\000\000\000\t\000pi\177\000\000(}\002pi\177\000\000\360<\034}i\177\000\000\234\312\344\000\000\000\000\000H\245\002pi\177\000\000\333\361\254\000\000\000\000\000\023\360\000\001", '\000' <repeats 12 times>, "`S\005pi\177\000\000\060|\002p\000\000\001\000\060=\034}i\177\000\000>\240\344\000\000\000\000\000\000\t\000pi\177\000\000\000\t\000pi\177\000\000\200=\034}i\177\000\000\231\310\344\000\000\000\000\000\240=\034}i\177\000\000l-d0t\b\000\000H\344\000\001\000\000\000\000\023\360\000\001\000\000\000\000\226"...,
  4. static m_namespace_to_wait_state_name = {
  5. {m_key = 101,
  6. m_name = 0xf125a2 "Waiting for global read lock", m_flags = 0},
  7. {m_key = 102,
  8. m_name = 0xf125c0 "Waiting for schema metadata lock", m_flags = 0},
  9. {m_key = 103,
  10. m_name = 0xf125e8 "Waiting for table metadata lock", m_flags = 0},
  11. {m_key = 104,
  12. m_name = 0xf12608 "Waiting for stored function metadata lock", m_flags = 0},
  13. {m_key = 105,
  14. m_name = 0xf12638 "Waiting for stored procedure metadata lock", m_flags = 0},
  15. {m_key = 106,
  16. m_name = 0xf12668 "Waiting for trigger metadata lock", m_flags = 0},
  17. {m_key = 107,
  18. m_name = 0xf12690 "Waiting for event metadata lock", m_flags = 0},
  19. {m_key = 108,
  20. m_name = 0xf126b0 "Waiting for commit lock", m_flags = 0}}}}

从上面的输出中,我们看到了最终是在test.ti上申请了事务级别的MDL_EXCLUSIVE锁。

会话3

  1. mysql> alter table ti stats_auto_recalc=1;
  2. Query OK, 0 rows affected (22 min 58.99 sec)
  3. Records: 0 Duplicates: 0 Warnings: 0

小结

本例只是简单的演示了,在同一个事务的不同时期加的不同的MDL的锁。MYSQL中DDL的操作不属于事务操作的范围。这就给mysql主备基于语句级别同步带来了困难。mysql主备在同步的过程中,为了保证主备结构一致性,而引入了MDL机制。为了尽可能的降低MDL带来的影响。请在业务低谷的时候,执行DDL操作。