共計 3269 個字符,預計需要花費 9 分鐘才能閱讀完成。
這篇文章將為大家詳細講解有關 MySQL 中如何使用 MDL 字典鎖,文章內容質量較高,因此丸趣 TV 小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
什么是 MDL
MDL,Meta Data lock,元數據鎖,一般稱為字典鎖。字典鎖與數據鎖相對應。字典鎖是為了保護數據對象被改變,一般是一些 DDL 會對字典對象改變,如兩個 TX,TX1 先查詢表,然后 TX2 試圖 DROP,字典鎖就會 lock 住 TX2,知道 TX1 結束(提交或回滾)。數據鎖是保護表中的數據,如兩個 TX 同時更新一行時,先得到 row lock 的 TX 會先執行,后者只能等待。
MDL 的設計目標
字典鎖在設計的時候是為了數據庫對象的元數據。到達以下 3 個目的。
1. 提供對并發訪問內存中字典對象緩存 (table definatin cache,TDC) 的保護。這是系統的內部要求。
2. 確保 DML 的并發性。如 TX1 對表 T1 查詢,TX2 同是對表 T1 插入。
3. 確保一些操作的互斥性,如 DML 與大部分 DDL(ALTER TABLE 除外)的互斥性。如 TX1 對表 T1 執行插入,TX2 執行 DROP TABLE,這兩種操作是不允許并發的,故需要將表對象保護起來,這樣可以保證 binlog 邏輯的正確性。(貌似之前的版本存在字典鎖是語句級的,導致 binlog 不合邏輯的 bug。)
支持的鎖類型
數據庫理論中的基本鎖類型是 S、X,意向鎖 IS、IX 是為了層次上鎖而引入的。比如要修改表中的數據,可能先對表上一個表級 IX 鎖,然后再對修改的數據上一個行級 X 鎖,這樣就可以保證其他試圖修改表定義的事物因為獲取不到表級的 X 鎖而等待。
MySQL 中將字典鎖的類型根據不同語句的功能,進一步細分,細分的依據是對字典的操作和對數據的操作。細分的好處是能在一定程度上提高并發效率,因為如果只定義 X 和 S 兩種鎖,必然導致兼容性矩陣的局限性。MySQL 不遺余力的定義了如下的鎖類型。
可以看到 MySQL 在 ALTER TABLE 的時候還是允許其他事務進行讀表操作的。需要注意的是讀操作的事物需要在 ALTER TABLE 獲取 MDL_SHARED_NO_WRITE 鎖之后,否則無法并發。這種應用場景應該是對一個較大的表進行 ALTER 時,其他事物仍然可以讀,并發性得到了提高。
鎖的兼容性
鎖的兼容性就是我們經常看到的那些兼容性矩陣,X 和 S 必然互斥,S 和 S 兼容。MySQL 根據鎖的類型我們也可以知道其兼容矩陣如下:
1 代表兼容,0 代表不兼容。你可能發現 X 和 IX 竟然兼容,沒錯,其實這里的 IX 已經不是傳統意義上的 IX,這個 IX 是用在范圍鎖上,所以和 X 鎖不互斥。
數據結構
涉及到的和鎖相關的數據結構主要是如下幾個:
MDL_context:字典鎖上下文。包含一個事物所有的字典鎖請求。
MDL_request:字典鎖請求。包含對某個對象的某種鎖的請求。
MDL_ticket:字典鎖排隊。MDL_request 就是為了獲取一個 ticket。
MDL_lock:鎖資源。一個對象全局唯一。可以允許多個可以并發的事物同時獲得。
涉及到的文件主要是 sql/mdl.cc
鎖資源
鎖資源在系統中是共享的,即全局的,存放在 static MDL_map mdl_locks; 的 hash 鏈表中,對于中的一個對象,其 hashkey 必然是唯一的,對應一個鎖資源。多個事務同時對一張表操作時,申請的 lock 也是同一個內存對象。獲取 mdl_locks 中的 lock 需要通過全局互斥量保護起來_mutex_lock(m_mutex); m_mutex 是 MDL_map 的成員。
上鎖流程
一個會話連接在實現中對應一個 THD 實體,一個 THD 對應一個 MDL_CONTEXT,表示需要的 mdl 鎖資源,一個 MDL_CONTEXT 中包含多個 MDL_REQUEST,一個 MDL_REQUEST 即是對一個對象的某種類型的 lock 請求。每個 mdl_request 上有一個 ticket 對象,ticket 中包含 lock。
上鎖的也就是根據 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,如果存在,則直接使用。否則創建一個 ticket,查找上鎖對象對應的 lock,沒有則創建。檢查 lock 是否可以被賦給本事務,如果可以直接返回,否則等待這個 lock;
鎖等待與喚醒
字典對象的鎖等待是發生在兩個事物對同一對象上不兼容的鎖導致的。當然,由于 lock 的唯一性,先到先得,后到的只能等待。
如何判斷一個 lock 是否可以 grant 給一個 TX?這需要結合 lock 結構來看了,lock 上有兩個成員,grant 和 wait,grant 代表此 lock 允許的事物都上了哪些鎖,wait 表示等待的事務需要上哪些鎖。其判斷一個事物是否可以 grant 的邏輯如下:
If(compatible(lock.grant, tx.locktype))
If (compatible(lock.wait, tx.locktype))
return can_grant;
End if
End if
即首先判斷 grant 中的鎖類型和當前事務是否兼容,然后判斷 wait 中的鎖類型和當前事務是否兼容。細心的話,會想到,wait 中的鎖類型是不需要和當前事務進行兼容性比較的,這是不是說這個比較是多余的了?其實也不是,因為 wait 的兼容性矩陣和上面的矩陣是不一樣的,wait 的兼容性矩陣感覺是在 DDL 等待的情況下,防止 DML 繼續進來(wait 矩陣就不寫出來了,大家可以去代碼里看下)。
比如:
TX1 TX2 TX3
SELECT T1
DROP T1
SELECT T1
這時候 TX2 會阻塞,TX3 也會阻塞,被 TX2 阻塞,也就是說被 wait 的事件阻塞了,這樣可能就是為了保證在 DDL 等待時,禁止再做 DML 了,因為在 DDL 面前,DML 顯得確實不是那么重要了。
如何喚醒被等待的事務呢?比如喚醒 TX2,當 TX1 結束時,會調用 release_all_locks_for_name,對被鎖住的事務進行喚醒,具體操作封裝在 reschedule_waiters 函數中,重置等待時間的標記位進行喚醒, 重點代碼如下:
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);
}
關于 MySQL 中如何使用 MDL 字典鎖就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。