前言:
Mysql是一個支持插件式存儲引擎的數據庫系統,本文討論的鎖機制也主要包含兩部分SERVER層的鎖和存儲引擎的鎖,存儲引擎是指innodb,其它存儲引暫不討論。
1. 數據庫中鎖相關的基本概念
1) 樂觀鎖,悲觀鎖
樂觀鎖和悲觀鎖都是一種並發控制策略。悲觀鎖假定多個事務會同時訪問同一個資源,采用的策略是“先上鎖,后訪問”,這種策略會有死鎖的風險。樂觀鎖相對於悲觀鎖而言,假定多個事務在運行過程中不會相互影響,寫入在讀取和寫入記錄時,不上鎖,取而代之是產生一個時間戳或版本號,事務提交階段,檢查記錄的版本號是否有被修改(若修改,則表示有其他事務讀寫),確定是否需要回滾事務。目前在數據庫領域,幾乎所有的DBMS都是采用悲觀鎖機制。
2) MVCC(Multi-Version Concurrency Control)
MVCC也是一種並發控制方法,MVCC對悲觀鎖控制機制做了改進,通過冗余數據的歷史版本,達到“讀不上鎖,讀寫不沖突”的效果,提高了並發效果。MVCC主要作用於讀提交和可重復讀兩種隔離級別上。
3) 兩階段鎖協議
所謂兩段鎖協議是指上鎖分為兩個階段,加鎖和解鎖階段,保證加鎖和解鎖階段不交錯。對於數據庫系統而言,事務開始時,處於加鎖階段;事務提交或rollback時,事務進入解鎖階段。只有滿足兩段鎖協議的數據庫系統,並發調度的事務才是可以串行化的。
4) 意向鎖
意向鎖機制約定如果對一個節點加鎖,必需先對它的上一層節點加意向鎖。比如,對一個記錄加鎖之前,首先對該記錄所在的表加意向鎖。意向鎖主要包括IS和IX,他們與S和X的兼容關系不在這里贅述。意向鎖的主要作用在於提高表鎖和行鎖沖突檢測效率。
5) 表鎖,記錄(行)鎖
表鎖和記錄鎖是鎖系統里面最基本的鎖。分別用於鎖定表和記錄。對於表而言,根據情況可以有S,X,IS和IX四種鎖類別;對表上IS和IX,表示需要讀記錄和寫記錄;記錄鎖則主要包括X鎖和S鎖。有關行鎖的實現,可以參考之前的文章,INNODB行鎖源碼學習
6) 字典鎖(metadata lock)
字典鎖是保護元數據的一種鎖,主要為了防止DDL和DML沖突的情況。有關MDL的詳細介紹,可以參考之前的文章,MYSQL METADATA LOCK
7) 死鎖
所謂死鎖是指兩個或多個事務,各自占有對方的期望獲得的資源,形成的循環等待,彼此無法繼續執行的一種狀態。
2. 舉個栗子
上面列了這么多種類的鎖,下面通過一個簡單的例子說明各種鎖是如何作用的,它們加鎖和釋放鎖的先后順序如何。這里假設隔離級別是RC,ID為主鍵。
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;
|
上字典鎖 |
GLOBAL:STATMENT MDL_INTENTION_EXCLUSIVE |
|
3 |
TABLE:TRANSACTION MDL_SHARED_WRITE |
|
||
4 |
上行鎖 |
|
LOCK_TABLE:IX (table:t3) |
|
5 |
|
LOCK_REC:X (id=1) |
||
6 |
執行更新 |
|
|
|
7 |
釋放MDL |
GLOBAL:STATMENT |
|
|
8 |
commit;
|
COMMIT 字典鎖 |
COMMIT: MDL_EXPLICIT MDL_INTENTION_EXCLUSIVE |
|
9 |
執行提交 |
|
|
|
10 |
釋放引擎鎖 |
|
lock_release |
|
11 |
釋放MDL
|
COMMIT: MDL_EXPLICIT MDL_INTENTION_EXCLUSIVE |
|
|
12 |
release_transactional_locks TABLE:TRANSACTION |
|
可以看到,第一行begin,表示開啟一個新事務,隱含提交會話的上一個事務,需要釋放之前的鎖。第2到7行是兩階段鎖中的上鎖階段,分別先后上了字典鎖、表的意向鎖和行鎖。上完鎖后,才開始真正的更新階段,從這里也可以看到MySQL的寫操作是符合悲觀鎖策略。第4行和第5行,我們可以看到意向鎖是如何運作的,上記錄id=1的行鎖之前,先對表t3上了意向鎖。第7行,語句執行完后,可以釋放STATEMENT級別的字典鎖,避免長時間持有鎖阻塞該表的DDL操作。8-12是提交階段,進入兩段鎖中的釋放鎖過程,先后釋放引擎層的表鎖和行鎖;然后釋放TRANSACTION級別的MDL鎖。
3. 常用語句加鎖分析
假設隔離級別:RC,id為主鍵
典型語句 |
SQL層面(MDL鎖) |
存儲引擎 innodb |
||
|
|
|||
范圍/對象 |
持有時間 |
表鎖 |
行鎖 |
|
SELECT操作 SELECT * FROM T |
TABLE: MDL_SHARED_READ |
MDL_TRANSACTION |
None |
None |
Show create table T |
TABLE: MDL_SHARED_HIGH_PRIO |
MDL_TRANSACTION |
|
|
LOCK TABLE T READ |
TABLE: MDL_SHARED_READ |
MDL_TRANSACTION |
None |
None |
LOCK TABLE T WRITE |
GLOBAL: MDL_INTENTION_EXCLUSIVE |
MDL_STATEMENT
|
None |
None |
SCHEMA: MDL_INTENTION_EXCLUSIVE TABLE: MDL_SHARED_NO_READ_WRITE
|
TRANSACTION |
|||
Flush table t with read lock |
TABLE: MDL_SHARED_NO_WRITE |
TRANSACTION |
None |
None |
Flush table with read lock |
GLOBAL: MDL_SHARED
|
MDL_EXPLICIT |
None |
None |
COMMIT: MDL_SHARED
|
MDL_EXPLICIT |
|||
DML操作 SELECT * FROM T FOR UPDATE; Update T set c1=? Where id=? |
GLOBAL: MDL_INTENTION_EXCLUSIVE |
MDL_STATEMENT |
IX |
X |
TABLE: MDL_SHARED_WRITE |
TRANSACTION
|
|||
DDL操作 Alter table t add column c1 int; Truncate table t; |
GLOBAL: MDL_INTENTION_EXCLUSIVE
|
MDL_STATEMENT |
None |
X |
SCHEMA: MDL_INTENTION_EXCLUSIVE TABLE: MDL_EXCLUSIVE |
TRANSACTION
|
|||
COMMIT: MDL_INTENTION_EXCLUSIVE |
MDL_EXPLICIT |
|||
Set global read_only=1; |
GLOBAL: MDL_SHARED COMMIT: MDL_SHARED |
MDL_EXPLICIT |
None |
None |