前言
MDL鎖主要用來保護Mysql內部對象的元數據,通過MDL機制保證DDL與DML以及SELECT查詢操作的並發。MySQL Meta Lock(一)和MySQL Meta Lock(二)已經講了一些關於MDL知識,本文將會對MDL進行一個補充,並解釋查詢堵塞和mysqldump獲取一致性備份的原理。
一、MDL鎖類型
1.按類型划分
2.按對象/范圍維度划分
屬性 |
含義 |
范圍/對象 |
GLOBAL |
全局鎖 |
范圍 |
COMMIT |
提交保護鎖 |
范圍 |
SCHEMA |
庫鎖 |
對象 |
TABLE |
表鎖 |
對象 |
FUNCTION |
函數鎖 |
對象 |
PROCEDURE |
存儲過程鎖 |
對象 |
TRIGGER |
觸發器鎖 |
對象 |
EVENT |
事件鎖 |
對象 |
MDL鎖主要包括DB對象和范圍兩個維度,對象的MDL我們很好理解,為了保護對象的元數據。那么范圍級別的鎖呢?鎖本質是為了保護共享資源,所以范圍和對象都可以理解為一種資源。MYSQL約定某些操作必需要上COMMIT范圍鎖或GLOBAL范圍鎖,通過這種同步機制,保證各個線程有序運轉。后面會結合案例詳細討論COMMIT和GLOBAL鎖的應用場景。
3.按請求/釋放鎖持續時間划分
屬性 |
含義 |
MDL_ STATEMENT |
語句級別 |
MDL_TRANSACTION |
事務級別 |
MDL_EXPLICIT |
需要顯示釋放 |
MDL另外一個屬性是持有時間。如果是STATEMENT,則表示單個語句執行完畢后,MDL釋放;TRANSACTION級別表示,事務結束后,MDL鎖釋放;前兩者都是隱式鎖,即請求鎖和釋放鎖都是系統內部行為,用戶無需發指令,而MDL_EXPLICIT表示MDL鎖是顯示請求和釋放的。比如:flush table with read lock這個指令會顯示上GLOBAL:MDL_EXPLICIT:SHARED和COMMIT:MDL_EXPLICIT:SHARED鎖;需要通過UNLOCK TABLES指令顯示釋放。
4.范圍鎖的兼容性矩陣
5.舉個栗子
begin:
update t3 set c1=1 where id=1;
commit;
流程 |
執行語句 |
執行內容 |
字典鎖 |
1 |
begin |
|
釋放MDL release_transactional_locks |
2 |
update t3 set c1=1 where id=1;
|
請求 STATEMENT MDL |
GLOBAL:STATMENT MDL_INTENTION_EXCLUSIVE |
3 |
請求 TRANSACTION MDL |
TABLE:TRANSACTION MDL_SHARED_WRITE |
|
6 |
執行更新 |
|
|
7 |
釋放 STATEMENT MDL |
GLOBAL:STATMENT |
|
8 |
commit;
|
請求 COMMIT MDL |
COMMIT: MDL_EXPLICIT MDL_INTENTION_EXCLUSIVE |
9 |
執行提交 |
|
|
11 |
釋放 COMMIT MDL 釋放 TRANSACTION MDL |
COMMIT: MDL_EXPLICIT MDL_INTENTION_EXCLUSIVE |
|
12 |
release_transactional_locks TABLE:TRANSACTION |
二、請求鎖/釋放鎖原理
鎖兼容矩陣在metadata lock(二)已經有詳細的介紹,仔細看代碼發現MDL的鎖兼容矩陣實際上包含兩部分:活躍鎖兼容矩陣,等待鎖兼容矩陣。當請求鎖時,需要保證兩個矩陣對應的值都兼容,才能請求鎖成功。為什么要設計等待鎖兼容矩陣?我理解這里主要是優先保證DDL操作。因為如果DDL操作在等待一個查詢操作時,其他查詢還源源不斷地進入,可能會導致DDL永遠也拿不到鎖,而實際情況下,DDL操作的重要性往往比查詢或DML重要地多。
1. 請求鎖兼容性檢查:
1) 檢查請求鎖是否與已經存在的活躍鎖沖突,若沖突,則等待;
2) 檢查請求鎖是否與已經存在的等待鎖沖突,若沖突,則等待。
3) 請求鎖成功。
2. 釋放鎖時機:
1) 語句執行結束后,釋放STAMENT類型的鎖
2) 事務提交時,先后請求COMMIT類型和釋放COMMIT類型的鎖
3) 事務提交后,釋放TRANSACTION類型的鎖
三、MDL應用場景分析
MDL是Mysql層面很重要的鎖,很多常見問題的源頭以及功能實現都依賴於MDL,以下我會舉幾個常見的問題和功能進行分析。
1. 為什么查詢也會被阻塞?
我們在實際運維過程中,一個常見的場景是,接到手機threadrunning飆高告警,登上主機,show processlist看到一大片線程處於“Waiting for table metadata lock”狀態,當然其中也包含查詢。下面我通過一個簡單的例子重新這個場景。
時間點 |
會話A |
會話B |
會話C |
|
1 |
begin update t3 set c1=1 where id=1; |
|
|
|
2 |
返回 |
|
|
|
3 |
|
alter table t3 add column c3 int; |
|
|
4 |
|
等待 |
|
|
5 |
|
|
select * from t3 |
|
6 |
|
|
等待 |
|
7 |
show processlist 返回結果 |
|
|
|
8 |
init |
show processlist |
|
|
Waiting for table metadata lock |
alter table t3 add column c3 int |
|||
Waiting for table metadata lock |
select * from t3 |
從表2可以看到A會話未提交的事務堵住了回話B的DDL語句,而DDL語句進而又堵住了會話C,從第8步來看,會話B和會話C都處於“Waiting for table metadata lock”狀態。從表1我們可以看到,會話A的DML操作會請求TABLE- TRANSACTION- MDL_SHARED_WRITE鎖,由於沒有執行COMMIT,會一直持有;會話B的DDL操作會請求TABLE-TRANSACTION-EXCLUSIVE鎖,由於兩把鎖互斥,等待;會話C的查詢操作會請求TABLE- TRANSACTION- MDL_SHARED_READ鎖,雖然MDL_SHARED_READ與活躍鎖MDL_SHARED_WRITE不沖突,但是與回話B的等待鎖EXCLUSIVE沖突,因此也會等待。遇到這種情況,首先要看看是否存在堵住的DDL,如果存在DDL,然后查詢是否有大查詢或者未提交的事務,這兩種情況都會導致DDL堵住,進而影響普通的查詢和DML操作。
2. Mysqldump與全局鎖
在實際生產環境中,為了容災和負載均衡,數據庫服務一般由一主一備一對實例組成,主庫對外提供讀寫服務,備庫提供只讀服務,或純粹為了容災使用。在這種體系下,通過mysqldump搭建新的實例時,需要獲得一個一致性備份集,並且獲得對應的位點(拉取主庫binlog的依據),通過全量+增量的方式復制一個數據庫實例。Mysqldump中為了保證一致性備份和獲取對應的位點,需要設置兩個關鍵的參數--master-data=2 和--single-transaction。我們可以通過mysqld的trace功能,跟蹤Mysqldump執行的語句。假設我們要備份chuck庫,命令如下:
./bin/mysqldump -uchuck -pchuck -P4006 –h127.0.0.1 --databases chuck mysql --master-data=2 --single-transaction --default-character-set=utf8 > chuck_dump.sql 2>chuck_dump.log
開啟mysqld的trace功能
--debug=d,query,general:O,/kkk/mysqld.trace
通過mysqld.trace,我大概整理了關鍵的語句,分析和結果如下圖所示。可以看到,mysqldump獲取增量位點主要是通過FLUSH TABLES WITH READ LOCK語句的兩把字典鎖來實現(GLOBAL LOCK和COMMIT LOCK)。整個過程持續時間不長(如果語句沒有被阻塞),獲取位點后,立即將MDL鎖釋放。第2到第6步即為持有MDL鎖的時間,這段時間內無法開啟新事務,已有的事務也無法提交,保證位點的正確性。設置隔離級別為RR,則是獲取一致性備份的關鍵,一致性備份的獲取源於MVCC,關於MVCC的實現,后續可以單獨寫一篇文章。第8到第9步為一個表的備份過程, select語句會獲取MDL鎖(TABLE:TRANSACTION: MDL_SHARED_READ),執行完畢后,通過rollback語句釋放MDL鎖。因此除了備份的當前表不能做DDL操作;當前表的DML,以及其它表的DDL和DML都不受影響,所以Mysqldump備份過程中,基本不會阻塞線上的其他語句執行。
步驟 |
關鍵語句 |
含義 |
作用 |
1 |
FLUSH /*!40101 LOCAL */ TABLES |
關閉打開表 |
清空查詢緩存 |
2 |
FLUSH TABLES WITH READ LOCK |
上GLOBAL字典鎖: GLOBAL: MDL_SHARED COMMIT: MDL_SHARED
|
阻塞新事務,以及活躍事務提交。 為獲取一致性位點做准備 |
3 |
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ |
設置事務的隔離級別為RR |
保證快照讀 |
4 |
START TRANSACTION /*!40100 WITH CONSISTENT SNAPSHOT */ |
開啟事務 |
|
5 |
SHOW MASTER STATUS |
獲取binlog 位點 |
用來獲取主庫的增量的位點
|
6 |
UNLOCK TABLES |
釋放 GLOBAL字典鎖 |
允許其它事務更新 |
7 |
SAVEPOINT sp |
設置保存點 |
后續可以逐個表 釋放MDL |
8 |
SELECT /*!40001 SQL_NO_CACHE */ * FROM `t1` |
獲取表t1的數據 TABLE:TRANSACTION MDL_SHARED_READ |
一致性讀 |
9 |
ROLLBACK TO SAVEPOINT sp |
釋放: TABLE:TRANSACTION MDL_SHARED_READ |
|
10 |
SELECT /*!40001 SQL_NO_CACHE */ * FROM `t2` |
獲取表t2的數據 TABLE:TRANSACTION MDL_SHARED_READ
|
一致性讀 |
11 |
ROLLBACK TO SAVEPOINT sp |
釋放: TABLE:TRANSACTION MDL_SHARED_READ |
|
12 |
…… |
|
|
13 |
|
|
備份完成 |
參考文章
http://www.percona.com/blog/2010/04/24/how-fast-is-flush-tables-with-read-lock/