深入理解MySQL系列之鎖

曹自標發表於2020-12-26

按鎖思想分類
  1. 悲觀鎖
  • 優點:適合在寫多讀少的併發環境中使用,雖然無法維持非常高的效能,但是在樂觀鎖無法提更好的效能前提下,可以做到資料的安全性
  • 缺點:加鎖會增加系統開銷,雖然能保證資料的安全,但資料處理吞吐量低,不適合在讀書寫少的場合下使用
  1. 樂觀鎖
  • 優點:在讀多寫少的併發場景下,可以避免資料庫加鎖的開銷,提高DAO層的響應效能,很多情況下ORM工具都有帶有樂觀鎖的實現,所以這些方法不一定需要我們人為的去實現。
  • 缺點:在寫多讀少的併發場景下,即在寫操作競爭激烈的情況下,會導致CAS多次重試,衝突頻率過高,導致開銷比悲觀鎖更高。
  • 實現:資料庫層面的樂觀鎖其實跟CAS思想類似, 通資料版本號或者時間戳也可以實現。
按鎖粒度分類
  1. 表鎖

表鎖是指對一整張表加鎖,一般是 DDL 處理時使用

表鎖由 MySQL Server 實現,表級鎖更適合於以查詢為主,併發使用者少,只有少量按索引條件更新資料的應用,如Web 應用

  • 優點:開銷小,加鎖快;不會出現死鎖;
  • 缺點:鎖定粒度大,發生鎖衝突的概率最高,併發度最低。
  1. 頁鎖:介於表鎖和行鎖之間。BDB 儲存引擎採用的是頁面鎖(page-level locking)

  2. 行鎖
    行鎖則是鎖定某一行或者某幾行,或者行與行之間的間隙。

行鎖則是儲存引擎實現,行級鎖更適合於有大量按索引條件併發更新少量不同資料,同時又有併發查詢的應用,如一些線上事務處理(OLTP)系統

  • 優點:鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。
  • 缺點:開銷大,加鎖慢;會出現死鎖;
InnoDB行鎖型別
  1. 標準
  • 共享鎖(S):允許一個事務去讀一行,阻止其他事務獲得相同資料集的排他鎖
  • 排他鎖(X):允許獲得排他鎖的事務更新資料,阻止其他事務取得相同資料集的共享讀鎖和排他寫鎖。

在這裡插入圖片描述

資料庫併發場景主要有三種:

  • 讀-讀:不存在任何問題,也不需要併發控制
  • 讀-寫:有隔離性問題,可能遇到髒讀,幻讀,不可重複讀
  • 寫-寫:可能存更新丟失問題,比如第一類更新丟失,第二類更新丟失
  1. 更細粒度
  • 共享鎖(S)
  • 排他鎖(X)
  • 意向共享鎖(IS):事務打算給資料行加行共享鎖,事務在給一個資料行加共享鎖前必須先取得該表的 IS 鎖。
  • 意向排他鎖(IX):事務打算給資料行加行排他鎖,事務在給一個資料行加排他鎖前必須先取得該表的 IX 鎖。

在這裡插入圖片描述

一致性(非)鎖定讀

一致性非鎖定讀:

一致性非鎖定讀由MVCC(多版本併發控制)實現。MVCC其實就是在每一行記錄後面增加兩個隱藏列,記錄建立版本號和刪除版本號,而每一個事務在啟動的時候,都有一個唯一的遞增的版本號

詳細可參考前文《深入理解MySQL系列之redo log、undo log和binlog》

一致性鎖定讀:
SELECT ... FOR UPDATE //加X鎖
SELECT ... LOCK IN SHARE MODE //加S鎖

鎖的演算法

行鎖的三種演算法:

  • Record Lock:當個行記錄上的鎖
  • Gap Lock:間隙鎖,鎖定一個範圍,但不包含記錄本身
  • Next Key Lock:Gap Lock + Record Lock ,鎖定一個範圍,並且鎖定記錄本身(只有REPEATABLE READ隔離級別有)

在這裡插入圖片描述

在預設事務隔離級別(REPEATABLE READ)下,InnoDB採用Next Key Lock解決幻讀(Phantom Problem)。對一個範圍加X鎖,從而這個範圍不允許插入,所以避免了幻讀。

此外,還可以用Next key lock實現唯一性檢查。如下,使用者通過索引查詢一個值,並對該行加一個S Lock;如果行不存在,鎖定一個範圍,新插入值也是唯一的。

SELECT * FROM TABLE WHERE COL = xxx LOCK IN SHARE MODE;
// 如果沒有找到任何行
INSERT INTO TABLE VALUES(...)

在這裡插入圖片描述

間隙鎖加鎖原則:

  • 1、加鎖的基本單位是 NextKeyLock,是前開後閉區間。
  • 2、查詢過程中訪問到的物件才會加鎖。
  • 3、索引上的等值查詢,給唯一索引加鎖的時候,NextKeyLock退化為行鎖。
  • 4、索引上的等值查詢,向右遍歷時且最後一個值不滿足等值條件的時候,NextKeyLock退化為間隙鎖。
  • 5、唯一索引上的範圍查詢會訪問到不滿足條件的第一個值為止。
自增鎖

AUTOINC 鎖又叫自增鎖(一般簡寫成 AI 鎖),是一種表鎖,當表中有自增列(AUTOINCREMENT)時出現。
當插入表中有自增列時,資料庫需要自動生成自增值,它會先為該表加 AUTOINC 表鎖,阻塞其他事務的插入操作,這樣保證生成的自增值肯定是唯一的。AUTOINC 鎖具有如下特點:
AUTO_INC 鎖互不相容,也就是說同一張表同時只允許有一個自增鎖;
自增值一旦分配了就會 +1,如果事務回滾,自增值也不會減回去,所以自增值可能會出現中斷的情況。
顯然,AUTOINC 表鎖會導致併發插入的效率降低,為了提高插入的併發性,MySQL 從 5.1.22 版本開始,引入了一種可選的輕量級鎖(mutex)機制來代替 AUTOINC 鎖,可以通過引數 innodbautoinclockmode 來靈活控制分配自增值時的併發策略。具體可以參考 MySQL 的 AUTOINCREMENT Handling in InnoDB 一文

死鎖

概念:死鎖是指兩個或兩個以上的事務執行過程中,因爭奪資源而造成的一種互相等待的現象。

  1. 解決死鎖第一種方法:超時,然後對事務回滾

缺點:若超時的事務所佔權重比較大,如事務更新了很多行,佔用了較多的undo log,這時採用FIFO方式就不合適了,因為回滾這個事務的時間相對另一個事務所佔用的時間可能會多很多。

  1. wait-for graph等待圖方式進行死鎖檢測

InnoDB採用的方式。

wait-for graph要求資料庫儲存兩種資訊,通過下面兩個連結串列構造出一張圖,如果這個圖中存在迴路,就代表存在死鎖,因此資源間發生相互等待。

  • 鎖的資訊連結串列
  • 事務等待連結串列

如下圖示例,事務等待連結串列有4個事務,而右邊鎖資訊連結串列中,row1中事務t1要等待事務t2資源,所以在下wait-for graph圖中就有一條t1指向t2的箭頭;而row2中tt2、t3需要等待t1中佔用row2資源,故t2、t3各有個箭頭指向t1(t1、t4兩個S鎖相容)。依次最後得到下面的圖。

可以發現(t1, t2)存在迴路,因此存在死鎖。

所以wait-for graph是一種主動的死鎖檢查機制,若存在死鎖,選擇回滾undo量最小的事務。(wait-for graph採用圖的深度優先演算法)

在這裡插入圖片描述

在這裡插入圖片描述

參考:
《MySQL技術內幕》
https://mp.weixin.qq.com/s/PPfXH9a3jlNyxXpwB_DZDg
https://zhuanlan.zhihu.com/p/29150809

相關文章