MySQL源碼學(xué)習(xí):MDL字典鎖
什么是MDL
MDL,Meta Data lock,元數(shù)據(jù)鎖,一般稱為字典鎖。字典鎖與數(shù)據(jù)鎖相對應(yīng)。字典鎖是為了保護數(shù)據(jù)對象被改變,一般是一些DDL會對字典對象改變,如兩個TX,TX1先查詢表,然后TX2試圖DROP,字典鎖就會lock住TX2,知道TX1結(jié)束(提交或回滾)。數(shù)據(jù)鎖是保護表中的數(shù)據(jù),如兩個TX同時更新一行時,先得到row lock的TX會先執(zhí)行,后者只能等待。
MDL的設(shè)計目標(biāo)
字典鎖在設(shè)計的時候是為了數(shù)據(jù)庫對象的元數(shù)據(jù)。到達以下3個目的。
1. 提供對并發(fā)訪問內(nèi)存中字典對象緩存(table definatin cache,TDC)的保護。這是系統(tǒng)的內(nèi)部要求。
2. 確保DML的并發(fā)性。如TX1對表T1查詢,TX2同是對表T1插入。
3. 確保一些操作的互斥性,如DML與大部分DDL(ALTER TABLE除外)的互斥性。如TX1對表T1執(zhí)行插入,TX2執(zhí)行DROP TABLE,這兩種操作是不允許并發(fā)的,故需要將表對象保護起來,這樣可以保證binlog邏輯的正確性。(貌似之前的版本存在字典鎖是語句級的,導(dǎo)致 binlog不合邏輯的bug。)
支持的鎖類型
數(shù)據(jù)庫理論中的基本鎖類型是S、X,意向鎖IS、IX是為了層次上鎖而引入的。比如要修改表中的數(shù)據(jù),可能先對表上一個表級IX鎖,然后再對修改的數(shù)據(jù)上一個行級X鎖,這樣就可以保證其他試圖修改表定義的事物因為獲取不到表級的X鎖而等待。
MySQL中將字典鎖的類型根據(jù)不同語句的功能,進一步細(xì)分,細(xì)分的依據(jù)是對字典的操作和對數(shù)據(jù)的操作。細(xì)分的好處是能在一定程度上提高并發(fā)效率,因為如果只定義X和S兩種鎖,必然導(dǎo)致兼容性矩陣的局限性。MySQL不遺余力的定義了如下的鎖類型。
名稱 |
意義 |
MDL_INTENTION_EXCLUSIVE |
意向排他鎖,只用于范圍上鎖 |
MDL_SHARED |
共享鎖,用于訪問字典對象,而不訪問數(shù)據(jù)。 |
MDL_SHARED_HIGH_PRIO |
只訪問字典對象(如DESC TABLE) |
MDL_SHARED_READ |
共享讀鎖,用于讀取數(shù)據(jù)(如select) |
MDL_SHARED_WRITE |
共享寫鎖,用于修改數(shù)據(jù)(如update) |
MDL_SHARED_NO_WRITE |
共享非寫鎖,允許讀取數(shù)據(jù),阻塞其他TX修改數(shù)據(jù)(如alter table) |
MDL_SHARED_NO_READ_WRITE |
用于訪問字典,讀寫數(shù)據(jù) 不允許其他TX讀寫數(shù)據(jù) |
MDL_EXCLUSIVE |
排他鎖,可以修改字典和數(shù)據(jù) |
可以看到MySQL在ALTER TABLE的時候還是允許其他事務(wù)進行讀表操作的。需要注意的是讀操作的事物需要在ALTER TABLE獲取MDL_SHARED_NO_WRITE鎖之后,否則無法并發(fā)。這種應(yīng)用場景應(yīng)該是對一個較大的表進行ALTER時,其他事物仍然可以讀,并發(fā)性得到了提高。
鎖的兼容性
鎖的兼容性就是我們經(jīng)常看到的那些兼容性矩陣,X和S必然互斥,S和S兼容。MySQL根據(jù)鎖的類型我們也可以知道其兼容矩陣如下:
|
IX |
S |
SH |
SR |
SW |
SNW |
SNRW |
X |
IX |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
S |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
0 |
SH |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
0 |
SR |
1 |
1 |
1 |
1 |
1 |
1 |
0 |
0 |
SW |
1 |
1 |
1 |
1 |
1 |
0 |
0 |
0 |
SNW |
1 |
1 |
1 |
1 |
0 |
0 |
0 |
0 |
SNRW |
1 |
1 |
1 |
0 |
0 |
0 |
0 |
0 |
X |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1代表兼容,0代表不兼容。你可能發(fā)現(xiàn)X和IX竟然兼容,沒錯,其實這里的IX已經(jīng)不是傳統(tǒng)意義上的IX,這個IX是用在范圍鎖上,所以和X鎖不互斥。
數(shù)據(jù)結(jié)構(gòu)
涉及到的和鎖相關(guān)的數(shù)據(jù)結(jié)構(gòu)主要是如下幾個:
MDL_context:字典鎖上下文。包含一個事物所有的字典鎖請求。
MDL_request:字典鎖請求。包含對某個對象的某種鎖的請求。
MDL_ticket:字典鎖排隊。MDL_request就是為了獲取一個ticket。
MDL_lock:鎖資源。一個對象全局唯一。可以允許多個可以并發(fā)的事物同時獲得。
涉及到的源碼文件主要是sql/mdl.cc
鎖資源
鎖資源在系統(tǒng)中是共享的,即全局的,存放在static MDL_map mdl_locks;的hash鏈表中,對于數(shù)據(jù)庫中的一個對象,其hashkey必然是唯一的,對應(yīng)一個鎖資源。多個事務(wù)同時對一張表操作時,申請的 lock也是同一個內(nèi)存對象。獲取mdl_locks中的lock需要通過全局互斥量保護起來 mysql_mutex_lock(&m_mutex); m_mutex是MDL_map的成員。
上鎖流程
一個會話連接在實現(xiàn)中對應(yīng)一個THD實體,一個THD對應(yīng)一個MDL_CONTEXT,表示需要的mdl鎖資源,一個MDL_CONTEXT中包含多個MDL_REQUEST,一個MDL_REQUEST即是對一個對象的某種類型的lock請求。每個mdl_request上有一個ticket對象,ticket中包含lock。
上鎖的也就是根據(jù)MDL_REQUEST進行上鎖。
- Acquire_lock:
- if (mdl_request contains the needed ticket )
- return ticket;
- End if;
- Create a ticket;
- If (!find lock in lock_sys)
- Create a lock;
- End if
- If (lock can be granted to mdl_request)
- Set lock to ticket;
- Set ticket to mdl_request;
- Else
- Wait for lock
- End if
稍微解釋下,首先是在mdl_request本身去查看有沒有相等的或者stronger的ticket,如果存在,則直接使用。否則創(chuàng)建一個 ticket,查找上鎖對象對應(yīng)的lock,沒有則創(chuàng)建。檢查lock是否可以被賦給本事務(wù),如果可以直接返回,否則等待這個lock;
鎖等待與喚醒
字典對象的鎖等待是發(fā)生在兩個事物對同一對象上不兼容的鎖導(dǎo)致的。當(dāng)然,由于lock的唯一性,先到先得,后到的只能等待。
如何判斷一個lock是否可以grant給一個TX?這需要結(jié)合lock結(jié)構(gòu)來看了,lock上有兩個成員,grant和wait,grant代表此 lock允許的事物都上了哪些鎖,wait表示等待的事務(wù)需要上哪些鎖。其判斷一個事物是否可以grant的邏輯如下:
- If(compatible(lock.grant, tx.locktype))
- If (compatible(lock.wait, tx.locktype))
- return can_grant;
- End if
- End if
即首先判斷grant中的鎖類型和當(dāng)前事務(wù)是否兼容,然后判斷wait中的鎖類型和當(dāng)前事務(wù)是否兼容。細(xì)心的話,會想到,wait中的鎖類型是不需要和當(dāng)前事務(wù)進行兼容性比較的,這是不是說這個比較是多余的了?其實也不是,因為wait的兼容性矩陣和上面的矩陣是不一樣的,wait的兼容性矩陣感覺是在 DDL等待的情況下,防止DML繼續(xù)進來(wait矩陣就不寫出來了,大家可以去代碼里看下)。
比如:
TX1 TX2 TX3
SELECT T1
DROP T1
SELECT T1
這時候TX2會阻塞,TX3也會阻塞,被TX2阻塞,也就是說被wait的事件阻塞了,這樣可能就是為了保證在DDL等待時,禁止再做DML了,因為在DDL面前,DML顯得確實不是那么重要了。
如何喚醒被等待的事務(wù)呢?比如喚醒TX2,當(dāng)TX1結(jié)束時,會調(diào)用release_all_locks_for_name,對被鎖住的事務(wù)進行喚醒,具體操作封裝在reschedule_waiters函數(shù)中,重置等待時間的標(biāo)記位進行喚醒,重點代碼如下:
- if (can_grant_lock(ticket->get_type(), ticket->get_ctx()))
- {
- if (! ticket->get_ctx()->m_wait.set_status(MDL_wait::GRANTED))
- {
- /*
- Satisfy the found request by updating lock structures.
- It is OK to do so even after waking up the waiter since any
- session which tries to get any information about the state of
- this lock has to acquire MDL_lock::m_rwlock first and thus,
- when manages to do so, already sees an updated state of the
- MDL_lock object.
- */
- m_waiting.remove_ticket(ticket);
- m_granted.add_ticket(ticket);
- }
今天把mdl系統(tǒng)總體上看了一下,對鎖的請求、等待以及喚醒有了初步了解。并發(fā)性的問題是最難調(diào)試的,大家如果想做鎖方面的實驗,可以利用VS調(diào)試中的凍結(jié)線程的功能,這樣就可以確保并發(fā)情況控制完全按照你設(shè)計思路去呈現(xiàn)。
原文鏈接:http://www.cnblogs.com/nocode/archive/2011/12/15/2289507.html


2010-11-22 14:27:05




