MySQL源碼學(xué)習(xí):MDL字典鎖
什么是MDL
MDL,Meta Data lock,元數(shù)據(jù)鎖,一般稱為字典鎖。字典鎖與數(shù)據(jù)鎖相對(duì)應(yīng)。字典鎖是為了保護(hù)數(shù)據(jù)對(duì)象被改變,一般是一些DDL會(huì)對(duì)字典對(duì)象改變,如兩個(gè)TX,TX1先查詢表,然后TX2試圖DROP,字典鎖就會(huì)lock住TX2,知道TX1結(jié)束(提交或回滾)。數(shù)據(jù)鎖是保護(hù)表中的數(shù)據(jù),如兩個(gè)TX同時(shí)更新一行時(shí),先得到row lock的TX會(huì)先執(zhí)行,后者只能等待。
MDL的設(shè)計(jì)目標(biāo)
字典鎖在設(shè)計(jì)的時(shí)候是為了數(shù)據(jù)庫(kù)對(duì)象的元數(shù)據(jù)。到達(dá)以下3個(gè)目的。
1. 提供對(duì)并發(fā)訪問內(nèi)存中字典對(duì)象緩存(table definatin cache,TDC)的保護(hù)。這是系統(tǒng)的內(nèi)部要求。
2. 確保DML的并發(fā)性。如TX1對(duì)表T1查詢,TX2同是對(duì)表T1插入。
3. 確保一些操作的互斥性,如DML與大部分DDL(ALTER TABLE除外)的互斥性。如TX1對(duì)表T1執(zhí)行插入,TX2執(zhí)行DROP TABLE,這兩種操作是不允許并發(fā)的,故需要將表對(duì)象保護(hù)起來,這樣可以保證binlog邏輯的正確性。(貌似之前的版本存在字典鎖是語句級(jí)的,導(dǎo)致 binlog不合邏輯的bug。)
支持的鎖類型
數(shù)據(jù)庫(kù)理論中的基本鎖類型是S、X,意向鎖IS、IX是為了層次上鎖而引入的。比如要修改表中的數(shù)據(jù),可能先對(duì)表上一個(gè)表級(jí)IX鎖,然后再對(duì)修改的數(shù)據(jù)上一個(gè)行級(jí)X鎖,這樣就可以保證其他試圖修改表定義的事物因?yàn)楂@取不到表級(jí)的X鎖而等待。
MySQL中將字典鎖的類型根據(jù)不同語句的功能,進(jìn)一步細(xì)分,細(xì)分的依據(jù)是對(duì)字典的操作和對(duì)數(shù)據(jù)的操作。細(xì)分的好處是能在一定程度上提高并發(fā)效率,因?yàn)槿绻欢xX和S兩種鎖,必然導(dǎo)致兼容性矩陣的局限性。MySQL不遺余力的定義了如下的鎖類型。
名稱 |
意義 |
MDL_INTENTION_EXCLUSIVE |
意向排他鎖,只用于范圍上鎖 |
MDL_SHARED |
共享鎖,用于訪問字典對(duì)象,而不訪問數(shù)據(jù)。 |
MDL_SHARED_HIGH_PRIO |
只訪問字典對(duì)象(如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的時(shí)候還是允許其他事務(wù)進(jìn)行讀表操作的。需要注意的是讀操作的事物需要在ALTER TABLE獲取MDL_SHARED_NO_WRITE鎖之后,否則無法并發(fā)。這種應(yīng)用場(chǎng)景應(yīng)該是對(duì)一個(gè)較大的表進(jìn)行ALTER時(shí),其他事物仍然可以讀,并發(fā)性得到了提高。
鎖的兼容性
鎖的兼容性就是我們經(jīng)??吹降哪切┘嫒菪跃仃嚕琗和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竟然兼容,沒錯(cuò),其實(shí)這里的IX已經(jīng)不是傳統(tǒng)意義上的IX,這個(gè)IX是用在范圍鎖上,所以和X鎖不互斥。
數(shù)據(jù)結(jié)構(gòu)
涉及到的和鎖相關(guān)的數(shù)據(jù)結(jié)構(gòu)主要是如下幾個(gè):
MDL_context:字典鎖上下文。包含一個(gè)事物所有的字典鎖請(qǐng)求。
MDL_request:字典鎖請(qǐng)求。包含對(duì)某個(gè)對(duì)象的某種鎖的請(qǐng)求。
MDL_ticket:字典鎖排隊(duì)。MDL_request就是為了獲取一個(gè)ticket。
MDL_lock:鎖資源。一個(gè)對(duì)象全局唯一??梢栽试S多個(gè)可以并發(fā)的事物同時(shí)獲得。
涉及到的源碼文件主要是sql/mdl.cc
鎖資源
鎖資源在系統(tǒng)中是共享的,即全局的,存放在static MDL_map mdl_locks;的hash鏈表中,對(duì)于數(shù)據(jù)庫(kù)中的一個(gè)對(duì)象,其hashkey必然是唯一的,對(duì)應(yīng)一個(gè)鎖資源。多個(gè)事務(wù)同時(shí)對(duì)一張表操作時(shí),申請(qǐng)的 lock也是同一個(gè)內(nèi)存對(duì)象。獲取mdl_locks中的lock需要通過全局互斥量保護(hù)起來 mysql_mutex_lock(&m_mutex); m_mutex是MDL_map的成員。
上鎖流程
一個(gè)會(huì)話連接在實(shí)現(xiàn)中對(duì)應(yīng)一個(gè)THD實(shí)體,一個(gè)THD對(duì)應(yīng)一個(gè)MDL_CONTEXT,表示需要的mdl鎖資源,一個(gè)MDL_CONTEXT中包含多個(gè)MDL_REQUEST,一個(gè)MDL_REQUEST即是對(duì)一個(gè)對(duì)象的某種類型的lock請(qǐng)求。每個(gè)mdl_request上有一個(gè)ticket對(duì)象,ticket中包含lock。
上鎖的也就是根據(jù)MDL_REQUEST進(jìn)行上鎖。
- 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)建一個(gè) ticket,查找上鎖對(duì)象對(duì)應(yīng)的lock,沒有則創(chuàng)建。檢查lock是否可以被賦給本事務(wù),如果可以直接返回,否則等待這個(gè)lock;
鎖等待與喚醒
字典對(duì)象的鎖等待是發(fā)生在兩個(gè)事物對(duì)同一對(duì)象上不兼容的鎖導(dǎo)致的。當(dāng)然,由于lock的唯一性,先到先得,后到的只能等待。
如何判斷一個(gè)lock是否可以grant給一個(gè)TX?這需要結(jié)合lock結(jié)構(gòu)來看了,lock上有兩個(gè)成員,grant和wait,grant代表此 lock允許的事物都上了哪些鎖,wait表示等待的事務(wù)需要上哪些鎖。其判斷一個(gè)事物是否可以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ì)心的話,會(huì)想到,wait中的鎖類型是不需要和當(dāng)前事務(wù)進(jìn)行兼容性比較的,這是不是說這個(gè)比較是多余的了?其實(shí)也不是,因?yàn)閣ait的兼容性矩陣和上面的矩陣是不一樣的,wait的兼容性矩陣感覺是在 DDL等待的情況下,防止DML繼續(xù)進(jìn)來(wait矩陣就不寫出來了,大家可以去代碼里看下)。
比如:
TX1 TX2 TX3
SELECT T1
DROP T1
SELECT T1
這時(shí)候TX2會(huì)阻塞,TX3也會(huì)阻塞,被TX2阻塞,也就是說被wait的事件阻塞了,這樣可能就是為了保證在DDL等待時(shí),禁止再做DML了,因?yàn)樵贒DL面前,DML顯得確實(shí)不是那么重要了。
如何喚醒被等待的事務(wù)呢?比如喚醒TX2,當(dāng)TX1結(jié)束時(shí),會(huì)調(diào)用release_all_locks_for_name,對(duì)被鎖住的事務(wù)進(jìn)行喚醒,具體操作封裝在reschedule_waiters函數(shù)中,重置等待時(shí)間的標(biāo)記位進(jìn)行喚醒,重點(diǎn)代碼如下:
- 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)總體上看了一下,對(duì)鎖的請(qǐng)求、等待以及喚醒有了初步了解。并發(fā)性的問題是最難調(diào)試的,大家如果想做鎖方面的實(shí)驗(yàn),可以利用VS調(diào)試中的凍結(jié)線程的功能,這樣就可以確保并發(fā)情況控制完全按照你設(shè)計(jì)思路去呈現(xiàn)。
原文鏈接:http://www.cnblogs.com/nocode/archive/2011/12/15/2289507.html






