1.1 lock 與 latch
latch 一般稱為閂鎖(輕量級的鎖),因為其要求鎖定時間必須非常短。在 InnoDB 存儲引擎中,latch 又分為 mutex 和 rwlock 。其目的是用來保證并發線程操作臨界資源的正確性,并且通常沒有死鎖檢測機制。
lock 的對象是事務,用來鎖定的是數據庫中的對象,如表、行、頁。并且一般 lock 的對象僅在事務 commit 或 rollback 后釋放,并且是有死鎖機制的。
lock | latch | |
---|---|---|
對象 | 事務 | 線程 |
保護 | 數據庫內容 | 內存數據結構 |
持續時間 | 整個事務過程 | 臨界資源 |
模式 | 行鎖、表鎖、意向鎖 | 讀寫鎖、互斥量 |
死鎖 | 通過 waits-for graph、time out 等機制進行檢測與處理 | 無死鎖檢測與處理機制 |
存在于 | Lock Manager 的哈希表中 | 每個數據結構的對象中 |
1.2 InnoDB 存儲引擎中的鎖
1.2.1 鎖的類型
- 共享鎖( S 鎖),允許事務讀一行數據(行級)
- 排他鎖( X 鎖),允許事務刪除或更新一行數據(行級)
- 意向共享鎖( IS 鎖),事務想要獲得一張表中某幾行的共享鎖(表級)
- 意向排他鎖( IX 鎖),事務想要獲得一張表中某幾行的排他鎖(表級)
這里說一下意向鎖。意向鎖是為了保證行鎖和表鎖可以共存。
考慮以下兩種情況:
- 想要對表加 S 鎖,那么就要先確定表中的行是否有 X 鎖,沒有的話才可以加表鎖
- 想要對表加 X 鎖,類似地,需要確定表里是否有 S 鎖或 X 鎖,沒有的話才可以加表鎖
而為了確定表中是否有鎖,很自然的就應該去遍歷表的每一行,確定每一行的鎖的情況。但是,這樣的效率太低了。因此,InnoDB 提出了意向鎖。在對每一行記錄加行鎖前,先對表加意向鎖,這樣當以后要需要對表加鎖時,就可以直接根據表的意向鎖來確定表里的加鎖情況。回到之前說的情況,現在想要對表加 S 鎖時,只需要看表上是否有 IX 鎖,沒有的話可以直接加表鎖。
IS | IX | S | X | |
---|---|---|---|---|
IS | 兼容 | 兼容 | 兼容 | 不兼容 |
IX | 兼容 | 兼容 | 不兼容 | 不兼容 |
S | 兼容 | 不兼容 | 兼容 | 不兼容 |
X | 不兼容 | 不兼容 | 不兼容 | 不兼容 |
需要注意的是表格上的 X 和 S 都是指的表鎖。
1.2.2 一致性非鎖定讀
InnoDB 通過行多版本控制的方法來讀取當前執行時間數據庫中行的數據。如果讀取的行上有 X 鎖,不會等待,而是去讀取行的上一個快照數據。這里也能理解為什么叫非鎖定讀了。
顯然,非鎖定讀極大地提高了數據庫的并發性,因此在默認情況下,這也是 InnoDB 的默認讀取方式。但是,不同的隔離級別下,讀取的方式不同!!!
- READ COMMITTED :讀取最新的一份快照數據
- REPEATABLE READ :讀取事務開始時的行數據版本
1.2.3 一致性鎖定讀
從上面可以知道,非鎖定讀雖然避免了加鎖的操作,但是讀到的數據不是最新的,而是歷史的數據。而在某些情況下是需要讀取實時的數據的,那么這時就不得不加鎖了。
SELECT … FOR UPDATE ( X 鎖)
SELECT … LOCK IN SHARE MODE ( S 鎖)
1.2.4 外鍵和鎖
在子表中對外鍵的值插入或更新,都需要先查詢父表的記錄,即 SELECT 父表。但是對于父表的 SELECT 操作并不是使用的一致性非鎖定讀的方式,而是對父表加 S 鎖。這種情況下,如果父表上已經有了 X 鎖,子表的操作只能被阻塞。
1.3 鎖的算法
- Record Lock :單個行記錄上的鎖
- Gap Lock :間隙鎖,鎖定一個范圍,但不包含記錄本身
- Next-Key Lock :鎖定一個范圍,并且鎖定記錄本身
需要明確的是,上面三種鎖都是對某行記錄加的。Record Lock 很好理解,就是對一行記錄加鎖。Gap Lock看起來有點奇怪,但是舉個例子就很清楚了。比如說一個索引有1,6,8,10 四個值,Gap Lock 可以分為以下幾個區間:(,1) , (1,6) , (6,8) , (8,10) , (10,
) 。那么對索引值為 6 的記錄加 Gap Lock 鎖的就是 (1,6) 。而 Next-Key Lock 實際上就是 Gap Lock+Record Lock,因此其鎖定的范圍應該是 (1,6] 。但實際上還會對下一個索引值加上 Gap Lock,因此實際鎖的范圍應該是 (1,6] + (6,8) 。很自然的我們會想為什么要有 Gap Lock,其實這是用來避免幻讀的,因此即使 InnoDB 默認的事務隔離級別是 REPEATABLE READ,幻讀也是不會發生的。還有一點需要注意的是,當查詢的索引含有唯一屬性(可以理解為包含唯一索引)時,InnoDB 會將 Next-Key Lock 降為 Record Lock。
1.4 死鎖
- 超時回滾
- wait-for graph,死鎖檢測
wait-for graph 要求數據庫保存以下兩種信息:
- 鎖的信息鏈表,確定圖中結點間的指向
- 事務等待鏈表,確定圖中的結點有哪些
從圖中可以看到 t1 和 t2 構成回路形成死鎖。在每個事務請求鎖并發生等待時,都會判斷是否存在回路,若存在就說明有死鎖,InnoDB 會回滾 undo 量最小的事務。