MySQL · 特性分析 · 利用gdb跟蹤MDL加鎖過程
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鎖的簡單示例
在實際工作中,最常見的MDL衝突就DDL的操作被沒用提交的事務所阻塞。 我們下麵通過一個具體的實例來演示DDL加MDL鎖的過程。在這個實例中,利用gdb來跟蹤DDL申請MDL鎖的過程。
會話1:
mysql> create table ti(id int primary key, c1 int, key(c1)) engine=InnoDB
stats_auto_recalc=default;
Query OK, 0 rows affected (0.03 sec)
mysql> insert into ti values (1,1), (2,2);
Query OK, 2 rows affected (0.03 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from ti;
+----+------+
| id | c1 |
+----+------+
| 1 | 1 |
| 2 | 2 |
+----+------+
2 rows in set (0.00 sec)
再開啟第二個會話,利用gdb來跟蹤mysql加MDL的過程
會話2:
[root@localhost mysql]# ps -ef|grep mysql
root 3336 2390 0 06:33 pts/2 00:00:01 /u02/mysql/bin/mysqld --basedir=/u02/mysql/ --datadir=/u02/mysql/data
--plugin-dir=/u02/mysql//lib/plugin --user=root
--log-error=/u02/mysql/tmp/error1.log --open-files-limit=10240
--pid-file=/u02/mysql/tmp/mysql.pid
--socket=/u02/mysql/tmp/mysql.sock --port=3306
[root@localhost mysql]# gdb -p 3336
----在GDB設置以下斷點
(gdb) b MDL_context::acquire_lock
Breakpoint 1 at 0x730cab: file /u02/mysql-server-5.6/sql/mdl.cc, line 2187.
(gdb) b lock_rec_lock
Breakpoint 2 at 0xb5ef50: file /u02/mysql-server-5.6/storage/innobase/lock/lock0lock.cc, line 2296.
(gdb) c
Continuing.....
開啟第三個會話
mysql> alter table ti stats_auto_recalc=1;
這個操作被hang住
在會話2中執行下麵的操作
(gdb) p mdl_request
$1 = (MDL_request *) 0x7f697d1c3bd0
(gdb) p *mdl_request
$2 = {
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,
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>,
"?\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"...,
static m_namespace_to_wait_state_name = {
{m_key = 101,
m_name = 0xf125a2 "Waiting for global read lock", m_flags = 0},
{m_key = 102,
m_name = 0xf125c0 "Waiting for schema metadata lock", m_flags = 0},
{m_key = 103,
m_name = 0xf125e8 "Waiting for table metadata lock", m_flags = 0},
{m_key = 104,
m_name = 0xf12608 "Waiting for stored function metadata lock", m_flags = 0},
{m_key = 105,
m_name = 0xf12638 "Waiting for stored procedure metadata lock", m_flags = 0},
{m_key = 106,
m_name = 0xf12668 "Waiting for trigger metadata lock", m_flags = 0},
{m_key = 107,
m_name = 0xf12690 "Waiting for event metadata lock", m_flags = 0},
{m_key = 108,
m_name = 0xf126b0 "Waiting for commit lock", m_flags = 0}}}}
(gdb)
從上麵的輸出中,我隻能看到申請了一個語句級別的MDL_INTENTION_EXCLUSIVE。並沒有看到什麼其他有意義的信息。我們繼續gdb跟蹤
(gdb) p *(mdl_request->next_in_list)
$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,
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",
static m_namespace_to_wait_state_name = {
{m_key = 101,
m_name = 0xf125a2 "Waiting for global read lock", m_flags = 0},
{m_key = 102,
m_name = 0xf125c0 "Waiting for schema metadata lock", m_flags = 0},
{m_key = 103,
m_name = 0xf125e8 "Waiting for table metadata lock", m_flags = 0},
{m_key = 104,
m_name = 0xf12608 "Waiting for stored function metadata lock", m_flags = 0},
{m_key = 105,
m_name = 0xf12638 "Waiting for stored procedure metadata lock", m_flags = 0},
{m_key = 106,
m_name = 0xf12668 "Waiting for trigger metadata lock", m_flags = 0},
{m_key = 107,
m_name = 0xf12690 "Waiting for event metadata lock", m_flags = 0},
{m_key = 108,
m_name = 0xf126b0 "Waiting for commit lock", m_flags = 0}}}}
從上麵的輸出中,我們看到了需要在test(見輸出中的 m_ptr = “\001test)數據庫上加一把事務級的MDL_INTENTION_EXCLUSIVE鎖。它並沒有告訴我們最終的MDL會落在哪個對象上。我們繼續跟蹤
$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,
m_ptr = "\002test\000ti", '\000' <repeats 378 times>,
static m_namespace_to_wait_state_name = {
{m_key = 101,
m_name = 0xf125a2 "Waiting for global read lock", m_flags = 0},
{m_key = 102,
m_name = 0xf125c0 "Waiting for schema metadata lock", m_flags = 0},
{m_key = 103,
m_name = 0xf125e8 "Waiting for table metadata lock", m_flags = 0},
{m_key = 104,
m_name = 0xf12608 "Waiting for stored function metadata lock", m_flags = 0},
{m_key = 105,
m_name = 0xf12638 "Waiting for stored procedure metadata lock", m_flags = 0},
{m_key = 106,
m_name = 0xf12668 "Waiting for trigger metadata lock", m_flags = 0},
{m_key = 107,
m_name = 0xf12690 "Waiting for event metadata lock", m_flags = 0},
{m_key = 108,
m_name = 0xf126b0 "Waiting for commit lock", m_flags = 0}}}}
從上麵的輸出中,我們可以看出最終是要在test數據庫的ti對象上加一把MDL_SHARED_UPGRADABLE鎖。在做DDL時會先加MDL_SHARED_UPGRADABLE鎖,然後升級到MDL_EXCLUSIVE鎖
我來執行下麵的過程
會話1
mysql> commit;
Query OK, 0 rows affected (5.51 sec)
會話2
(gdb) p *mdl_request
$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,
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"...,
static m_namespace_to_wait_state_name = {
{m_key = 101,
m_name = 0xf125a2 "Waiting for global read lock", m_flags = 0},
{m_key = 102,
m_name = 0xf125c0 "Waiting for schema metadata lock", m_flags = 0},
{m_key = 103,
m_name = 0xf125e8 "Waiting for table metadata lock", m_flags = 0},
{m_key = 104,
m_name = 0xf12608 "Waiting for stored function metadata lock", m_flags = 0},
{m_key = 105,
m_name = 0xf12638 "Waiting for stored procedure metadata lock", m_flags = 0},
{m_key = 106,
m_name = 0xf12668 "Waiting for trigger metadata lock", m_flags = 0},
{m_key = 107,
m_name = 0xf12690 "Waiting for event metadata lock", m_flags = 0},
{m_key = 108,
m_name = 0xf126b0 "Waiting for commit lock", m_flags = 0}}}}
從上麵的輸出中,我們看到了最終是在test.ti上申請了事務級別的MDL_EXCLUSIVE鎖。
會話3
mysql> alter table ti stats_auto_recalc=1;
Query OK, 0 rows affected (22 min 58.99 sec)
Records: 0 Duplicates: 0 Warnings: 0
小結
本例隻是簡單的演示了,在同一個事務的不同時期加的不同的MDL的鎖。MYSQL中DDL的操作不屬於事務操作的範圍。這就給mysql主備基於語句級別同步帶來了困難。mysql主備在同步的過程中,為了保證主備結構一致性,而引入了MDL機製。為了盡可能的降低MDL帶來的影響。請在業務低穀的時候,執行DDL操作。
最後更新:2017-09-21 09:03:42