首頁 > 軟體

詳解MySql中InnoDB儲存引擎中的各種鎖

2022-02-11 13:02:48

什麼是鎖

現實生活中的鎖是為了保護你的私有物品,在資料庫中鎖是為了解決資源爭搶的問題,鎖是資料庫系統區別於檔案系統的一個關鍵特性。鎖機制用於管理對共用資源的並行訪。

資料庫系統使用鎖是為了支援對共用資源進行並行存取,提供資料的完整性和一致性

InnoDB儲存引擎區別於MyISAM的兩個重要特徵就是:InnoDB儲存引擎支援事務和行級別的鎖,MyISAM只支援表級別的鎖

InnoDB儲存引擎中的鎖

InnoDB儲存引擎實現瞭如下兩種標準的行級鎖:

共用鎖(S Lock),允許事務讀一行資料排他鎖(X Lock),允許事務刪除或更新一行資料

鎖的相容性

-XS
X不相容不相容
S不相容相容

可以看到,X排他鎖不與其他鎖相容,S共用鎖只與S相容

此外,InnoDB儲存引擎支援多粒度(granular)鎖定,這種鎖定允許事務在行級上的鎖和表級上的鎖同時存在

為了支援在不同粒度上進行加鎖操作,InnoDB儲存引擎支援一種額外的鎖方式,稱之為意向鎖(Intention Lock)。

意向鎖是將鎖定的物件分為多個層次,意向鎖意味著事務希望在更細粒度(fine granularity)上進行加鎖

如上圖,資料庫從上到下可以分為資料庫、表、頁、記錄四個層次,行記錄是最細粒度的鎖,我們在獲取行鎖的時候,需要從上到下各個級別分別進行鎖定,最後才能獲取到行鎖。比如,你要獲取行記錄x的鎖,需要先在資料庫、表、頁上加意向鎖IX,其中任何一方需要等待鎖,會造成行鎖的等待

InnoDB儲存引擎支援的意向鎖即為表級別的鎖。支援兩種意向鎖

  • 意向共用鎖(IS Lock),事務想要獲得一張表中某幾行的共用鎖
  • 意向排他鎖(IX Lock),事務想要獲得一張表中某幾行的排他鎖

由於InnoDB儲存引擎支援的是行級別的鎖,因此意向鎖其實不會阻塞除全表掃以外的任何請求。故表級意向鎖與行級鎖的相容性如表所示

-ISIXSX
IS相容相容相容不相容
IX相容相容不相容不相容
S相容不相容相容不相容
X不相容不相容不相容不相容

一致性非鎖定讀

一致性的非鎖定讀(consistent nonlocking read)是指InnoDB儲存引擎通過行多版本控制(multi versioning)的方式來讀取當前執行時間資料庫中行的資料。

如果讀取的行正在執行DELETE或UPDATE操作,這時讀取操作不會因此去等待行上鎖的釋放。

相反地,InnoDB儲存引擎會去讀取行的一個快照資料

快照資料是指該行的之前版本的資料,該實現是通過undo段來完成。而undo用來在事務中回滾資料,因此快照資料本身是沒有額外的開銷。此外,讀取快照資料是不需要上鎖的,因為沒有事務需要對歷史的資料進行修改操作

快照資料其實就是當前行資料之前的歷史版本,每行記錄可能有多個版本。如上圖,記錄B就有多個歷史的快照版本
這就是大名鼎鼎的MVCC

多版本並行控制(Multi Version Concurrency Control,MVCC)是指一個行記錄有多個快照版本,由多個快照版本引發的並行控制,叫做多版本並行控制

那這麼多歷史的快速版本,存取的時候該用哪一個呢?

  • 在READ COMMITTED事務隔離級別下,非一致性讀總是讀取被鎖定行的最新一份快照資料
  • 在REPEATABLE READ事務隔離級別下,非一致性讀總是讀取事務開始時的行資料版本

由此可見,不同的事務隔離級別在MVCC的處理上還不一樣

一致性鎖定讀

在預設設定下,即事務的隔離級別為REPEATABLE READ模式下,InnoDB儲存引擎的SELECT操作使用一致性非鎖定讀

但是在某些情況下,使用者需要顯式地對資料庫讀取操作進行加鎖以保證資料邏輯的一致性

InnoDB儲存引擎對於SELECT語句支援兩種一致性的鎖定讀(locking read)操作:

  • SELECT…FOR UPDATE 對讀取的行記錄加一個X鎖,其他事務不能對已鎖定的行加上任何鎖
  • SELECT…LOCK IN SHARE MODE對讀取的行記錄加一個S鎖,其他事務可以向被鎖定的行加S鎖,但是如果加X鎖,則會被阻塞

對於一致性非鎖定讀,即使讀取的行已被執行了SELECT…FOR UPDATE,也是可以進行讀取的

一致性鎖定讀則需要檢查被讀取的行上有沒有互斥的鎖,假如有互斥的鎖存在就需要等待鎖的釋放

鎖的演演算法

行鎖的3種演演算法

  • Record Lock:單個行記錄上的鎖
  • Gap Lock:間隙鎖,鎖定一個範圍,但不包含記錄本身
  • Next-Key Lock∶Gap Lock+Record Lock,鎖定一個範圍,並且鎖定記錄本身

Record Lock總是會去鎖住索引記錄,如果InnoDB儲存引擎表在建立的時候沒有設定任何一個索引,那麼這時InnoDB儲存引擎會使用隱式的主鍵來進行鎖定

Gap Lock的作用是為了阻止多個事務將記錄插入到同一範圍內,而這會導致幻讀問題的產生

使用者可以通過以下兩種方式來顯式地關閉Gap Lock:

  • 將事務的隔離級別設定為READ COMMITTED
  • 將引數innodb_locks_unsafe_for_binlog設定為1

在上述的設定下,除了外來鍵約束和唯一性檢查依然需要的Gap Lock,其餘情況僅使用RecordLock進行鎖定。

但需要牢記的是,上述設定破壞了事務的隔離性,並且對於replication,可能會導致主從資料的不一致。

Next-Key Lock是結合了Gap Lock和Record Lock的一種鎖定演演算法,在Next-Key Lock演演算法下,InnoDB對於行的查詢都是採用這種鎖定演演算法

當查詢的索引含有唯一屬性時,InnoDB儲存引擎會對Next-Key Lock進行優化,將其降級為Record Lock,即僅鎖住索引本身,而不是範圍

幻像問題

幻像問題(Phantom Problem)是指在同一事務下,連續執行兩次同樣的SQL語句可能導致不同的結果,第二次的SQL語句可能會返回之前不存在的行

看下面的場景:

  • 表t由1、2、5這三個值組成
  • 執行select * from t where a > 2 for update;
  • 上述事務T1並沒有提交,那麼此時另一個事務T2插入4這個值,並且資料庫允許這個操作
  • 那麼事務T1再執行上述查詢,就得到4、5兩筆記錄,跟第一次得到的結果不一樣,違反了事務的隔離性

InnoDB儲存引擎採用Next-Key Locking的演演算法避免幻像問題。對於上述的SQL語句select * from t where a > 2 for update,其鎖住的不是5這單個值,而是對(2,+∞)這個範圍加了X鎖。因此任何對於這個範圍的插入都是不被允許的,從而避免幻像問題

InnoDB儲存引擎預設的事務隔離級別是REPEATABLE READ,在該隔離級別下,其採用Next-Key Locking的方式來加鎖

而在事務隔離級別READ COMMITTED下,其僅採用RecordLock行鎖

鎖的問題

髒讀

髒讀指的就是在不同的事務下,當前事務可以讀到另外事務未提交的資料,簡單來說就是可以讀到髒資料

髒讀發生的條件是需要事務的隔離級別為READ UNCOMMITTED,而目前絕大部分的資料庫都至少設定成READCOMMITTED。

InnoDB儲存引擎預設的事務隔離級別為READ REPEATABLE,Microsoft SQLServer資料庫為READ COMMITTED,Oracle資料庫同樣也是READ COMMITTED

不可重複讀

不可重複讀是指在一個事務內多次讀取同一資料集合。在這個事務還沒有結束時,另外一個事務也存取該同一資料集合,並做了一些DML操作。因此,在第一個事務中的兩次讀資料之間,由於第二個事務的修改,那麼第一個事務兩次讀到的資料可能是不一樣的。這樣就發生了在一個事務內兩次讀到的資料是不一樣的情況,這種情況稱為不可重複讀

不可重複讀和髒讀的區別是:髒讀是讀到未提交的資料,而不可重複讀讀到的卻是已經提交的資料,但是其違反了資料庫事務一致性的要求

一般來說,不可重複讀的問題是可以接受的,因為其讀到的是已經提交的資料,本身並不會帶來很大的問題。因此,很多資料庫廠商(如Oracle、Microsoft SQL Server)將其資料庫事務的預設隔離級別設定為READ COMMITTED,在這種隔離級別下允許不可重複讀的現象

在InnoDB儲存引擎中,通過使用Next-Key Lock演演算法來避免不可重複讀的問題。

在MySQL官方檔案中將不可重複讀的問題定義為Phantom Problem,即幻像問題。在Next-Key Lock演演算法下,對於索引的掃描,不僅是鎖住掃描到的索引,而且還鎖住這些索引覆蓋的範圍(gap)。

因此在這個範圍內的插入都是不允許的。這樣就避免了另外的事務在這個範圍內插入資料導致的不可重複讀的問題。因此,InnoDB儲存引擎的預設事務隔離級別是READ REPEATABLE,採用Next-Key Lock演演算法,避免了不可重複讀的現象

丟失更新

丟失更新是另一個鎖導致的問題,簡單來說其就是一個事務的更新操作會被另一個事務的更新操作所覆蓋,從而導致資料的不一致

  • 事務T1將行記錄r更新為v1,但是事務T1並未提交
  • 與此同時,事務T2將行記錄r更新為v2,事務T2未提交
  • 事務T1提交
  • 事務T2提交

在當前資料庫的任何隔離級別下,都不會導致資料庫理論意義上的丟失更新問題。這是因為,即使是READ UNCOMMITTED的事務隔離級別,對於行的DML操作,需要對行或其他粗粒度級別的物件加鎖

死鎖

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

解決死鎖問題最簡單的一種方法是超時,即當兩個事務互相等待時,當一個等待時間超過設定的某一閾值時,其中一個事務進行回滾,另一個等待的事務就能繼續進行。在InnoDB儲存引擎中,引數innodb_lock_wait_timeout用來設定超時的時間

超時機制雖然簡單,但是其僅通過超時後對事務進行回滾的方式來處理,或者說其是根據FIFO的順序選擇回滾物件。但若超時的事務所佔權重比較大,如事務操作更新了很多行,佔用了較多的undo log,這時採用FIFO的方式,就顯得不合適了,因為回滾這個事務的時間相對另一個事務所佔用的時間可能會很多

因此,除了超時機制,當前資料庫還都普遍採用wait-for graph(等待圖)的方式來進行死鎖檢測。

較之超時的解決方案,這是一種更為主動的死鎖檢測方式。InnoDB儲存引擎也採用的這種方式。

wait-for graph要求資料庫儲存以下兩種資訊

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

wait-for graph的死鎖檢測通常採用深度優先的演演算法實現,通常來說InnoDB儲存引擎選擇回滾undo量最小的事務

到此這篇關於詳解MySql中InnoDB儲存引擎中的各種鎖的文章就介紹到這了,更多相關詳解MySql中InnoDB儲存引擎中的各種鎖內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com