從根兒上理解MySQL | 鎖

目錄

InnoDB存儲引擎中的鎖

InnoDB中的行級鎖

InnoDB中的表級鎖

MySQL語句加鎖分析

普通的SELECT語句

鎖定讀語句

INSERT語句


InnoDB存儲引擎中的鎖

InnoDB中的行級鎖

  • Record Locks

官方的類型名稱爲:LOCK_REC_NOT_GAP,記錄鎖又分爲S鎖和X鎖:

  1. S鎖:共享鎖,英文名:Shared Locks。在事務要讀取一條記錄時,需要先獲取該記錄的S鎖
  2. X鎖:獨佔鎖,也常稱排他鎖,英文名:Exclusive Locks。在事務要改動一條記錄時,需要先獲取該記錄的X鎖

當一個事務獲取了一條記錄的S鎖後,其他事務也可以繼續獲取該記錄的S鎖,但不可以繼續獲取X鎖;當一個事務獲取了一條記錄的X鎖後,其他事務既不可以繼續獲取該記錄的S鎖,也不可以繼續獲取X鎖。也就是說,S鎖S鎖是兼容的,S鎖X鎖是不兼容的,X鎖X鎖也是不兼容的。

  • Gap Locks

官方的類型名稱爲:LOCK_GAP,間隙鎖的提出僅僅是爲了防止插入幻影記錄,如果我們對一條記錄加了gap鎖,並不會限制其他事務對這條記錄加記錄鎖或者繼續加gap鎖。假設我們把number值爲8的那條記錄加一個gap鎖(如下圖所示),這意味着不允許別的事務在number值爲8的記錄前邊的間隙(3, 8)這個區間插入新記錄;如果我們要阻止其他事務插入number值在(20, +∞)這個區間的新記錄,可以在number值爲20的那條記錄所在頁面的Supremum記錄加上一個gap鎖。

  • Next-Key Locks

官方的類型名稱爲:LOCK_ORDINARY,next-key鎖的本質就是一個記錄鎖和一個gap鎖的合體,它既能保護該條記錄,又能阻止別的事務將新記錄插入被保護記錄前邊的間隙

  • Insert Intention Locks

官方的類型名稱爲:LOCK_INSERT_INTENTION。一個事務在插入一條記錄時需要判斷一下插入位置是不是被別的事務加了所謂的gap鎖,如果有的話,插入操作需要等待,在等待時事務需要在內存中生成一個鎖結構,表明有事務想在某個間隙中插入新記錄,但是現在在等待,而這個鎖結構就是插入意向鎖。比方說現在T1number值爲8的記錄加了一個gap鎖,然後T2T3分別想向hero表中插入number值分別爲45的兩條記錄,所以現在爲number值爲8的記錄加的鎖的示意圖就如下所示:

  • 隱式鎖

一個事務對新插入的記錄可以不顯式的加鎖(生成一個鎖結構),但是由於事務id的存在,相當於加了一個隱式鎖別的事務在對這條記錄加S鎖或者X鎖時,由於隱式鎖的存在,會先幫助當前事務生成一個鎖結構,然後自己再生成一個鎖結構後進入等待狀態

InnoDB中的表級鎖

  • 表級別的S鎖、X鎖

如果一個事務給表加了S鎖,別的事務可以繼續獲得該表或表中某些記錄的S鎖,但不可以繼續獲得該表或表中某些記錄的X鎖;如果一個事務給表加了X鎖,也就意味着該事務要獨佔這個表,別的事務既不可以繼續獲得該表的S鎖,也不可以繼續獲得該表的X鎖。

表級別的S鎖、X鎖瞭解即可,一般情況下不會使用。

  • 表級別的IS鎖IX鎖
  1. IS鎖:意向共享鎖,當事務準備在某條記錄上加S鎖時,需要先在表級別加一個IS鎖
  2. IX鎖:意向獨佔鎖,當事務準備在某條記錄上加X鎖時,需要先在表級別加一個IX鎖

IS、IX鎖的提出僅僅爲了在之後加表級別的S鎖和X鎖時可以快速判斷表中的記錄是否被上鎖,以避免用遍歷的方式來查看錶中有沒有上鎖的記錄,也就是說其實IS鎖和IX鎖是兼容的,IX鎖和IX鎖是兼容的。

  • 表級別的AUTO-INC鎖

如果插入語句在執行前不可以確定具體要插入多少條記錄,一般是使用AUTO-INC鎖爲AUTO_INCREMENT修飾的列生成對應的值。一個事務在持有AUTO-INC鎖的過程中,其他事務的插入語句都要被阻塞,這樣可以保證一個語句中分配的遞增值是連續的。

MySQL語句加鎖分析

普通的SELECT語句

  • 未提交讀:不加鎖,直接讀取記錄的最新版本,可能發生髒讀不可重複讀幻讀問題。
  • 提交讀:不加鎖,在每次執行普通的SELECT語句時都會生成一個ReadView,這樣解決了髒讀問題,但沒有解決不可重複讀幻讀問題。
  • 可重複讀:不加鎖,只在第一次執行普通的SELECT語句時生成一個ReadView,這樣把髒讀不可重複讀幻讀問題都解決了。

注意:InnoDB中的MVCC並不能完完全全的禁止幻讀。假設一個事務T1第一次執行普通的SELECT語句時生成了一個ReadView,之後T2hero表中新插入了一條記錄便提交了,ReadView並不能阻止T1執行UPDATE或者DELETE語句來對改動這個新插入的記錄,但是這樣一來這條新記錄的trx_id隱藏列就變成了T1事務id,之後T1中再使用普通的SELECT語句去查詢這條記錄時就可以看到這條記錄。

  • 串行化:如果系統變量autocommit=0(禁用自動提交),普通的SELECT語句會被轉爲SELECT ... LOCK IN SHARE MODE,也就是在讀取記錄前需要先獲得記錄的S鎖,具體的加鎖情況和REPEATABLE READ隔離級別下一樣;如果系統變量autocommit=1(啓用自動提交),不加鎖,只是利用MVCC來生成一個ReadView去讀取記錄。

鎖定讀語句

先介紹兩種特殊的SELECT語句:

1. 對讀取的記錄加S鎖

SELECT ... LOCK IN SHARE MODE;

2. 對讀取的記錄加X鎖

SELECT ... FOR UPDATE;

以上兩種特殊的SELECT語句就是鎖定讀,另外UPDATE語句DELETE語句在執行過程需要首先定位到被改動的記錄並給記錄加鎖,也可以被認爲是一種鎖定讀。注意:採用加鎖方式解決併發事務帶來的問題時,髒讀不可重複讀在任何一個隔離級別下都不會發生。

  • 未提交讀/提交讀隔離級別下

對聚簇索引中的記錄(和二級索引中的記錄)加S鎖 / X鎖。注意:對於DELETE語句和UPDATE語句(更新了二級索引時),如果是利用主鍵進行等值查詢,是先爲聚簇索引記錄加X鎖,再爲對應的二級索引記錄加X鎖;而如果使用二級索引進行等值查詢,是先對二級索引記錄加S / X鎖,然後再給對應的聚簇索引記錄加S / X鎖。另外,如果進行範圍查詢,比如利用主鍵進行範圍查詢,會先在聚簇索引中定位到滿足該範圍的第一條記錄,然後沿着由記錄組成的單向鏈表一路向後找,每找到一條記錄,就會爲其加上S鎖 / X鎖,然後判斷該記錄符不符合範圍查詢的邊界條件,不符合就結束查詢。

  • 可重複讀隔離級別下

採用加鎖的方式解決併發事務產生的問題時,可重複讀隔離級別與未提交讀和提交讀這兩個隔離級別相比,最主要的就是要解決幻讀問題,而解決幻讀問題靠的是間隙鎖

1. 使用主鍵進行等值查詢:如果主鍵值存在,由於主鍵的唯一性,不可能發生幻讀,所以只要爲該記錄加S鎖 / X鎖;如果主鍵值不存在,就需要在第一個大於該值的主鍵值所在的記錄加一個間隙鎖

2. 使用主鍵進行範圍查詢:以SELECT * FROM hero WHERE number <= 8 LOCK IN SHARE MODE語句爲例,加鎖情況如下所示:

該語句會爲13815這4條記錄都加上S型next-key鎖,特別注意的是,REPEATABLE READ隔離級別下,在判斷number值爲15的記錄不滿足邊界條件 number <= 8 後,並不會去釋放加在該記錄上的鎖(注意和未提交讀、可提交讀區分)。

使用SELECT ... FOR UPDATE語句只是將上述S型next-key鎖替換成X型next-key鎖。對於DELETE語句和UPDATE語句(更新了二級索引時),以UPDATE hero SET name = 'cao曹操' WHERE number <= 8;語句爲例:

會對number值爲13815的聚簇索引記錄加X型next-key鎖,相應的爲number值爲138的聚簇索引記錄對應的idx_name二級索引記錄加X鎖需要注意的是並不會對number值爲15的記錄對應的二級索引記錄加鎖。

3.使用唯一二級索引進行等值/範圍查詢:與使用主鍵進行等值/範圍查詢類似,不同的是先在二級索引加間隙鎖/next-key鎖(後在聚簇索引加S鎖/X鎖)。

4.使用普通二級索引進行等值查詢:以SELECT * FROM hero WHERE name = 'c曹操' LOCK IN SHARE MODE;語句爲例:對所有name值爲'c曹操'的二級索引記錄加S型next-key鎖,它們對應的聚簇索引記錄加S鎖(值不存在自然就不加了);然後對最後一個name值爲'c曹操'的二級索引記錄的下一條二級索引記錄加間隙鎖

5.使用普通二級索引進行範圍查詢:與使用唯一二級索引的加鎖情況類似。

6.全表掃描:存儲引擎每讀取一條聚簇索引記錄,就會爲這條記錄加鎖一個S型next-key鎖,然後返回給server層判斷條件是否成立,如果成立則將其發送給客戶端,否則會向InnoDB存儲引擎發送釋放掉該記錄上的鎖的消息,但在可重複讀隔離級別下,InnoDB存儲引擎並不會真正的釋放掉鎖,所以聚簇索引的全部記錄都會被加鎖,並且在事務提交前不釋放。

INSERT語句

INSERT語句一般情況下不加鎖,不過當前事務在插入一條記錄前需要先定位到該記錄在B+樹中的位置,如果該位置的下一條記錄已經被加了間隙鎖,那麼當前事務會在該記錄上加上插入意向鎖,並且事務進入等待狀態。下面討論INSERT語句可能遇到的兩種特殊情況:

  • 遇到重複鍵

在定位到新記錄應該插入到B+樹的位置時,如果發現有已存在記錄的主鍵或者唯一二級索引列,那麼此時是會報錯的。但在生成報錯信息之前,如果是主鍵值重複,會對聚簇索引中相應的記錄加鎖,在未提交讀/提交讀隔離級別下,會加S鎖,在可重複讀隔離級別下加S型next-key鎖;而如果是唯一二級索引列值重複,無論是哪種隔離級別,會對已經在B+樹中的唯一二級索引記錄加next-key鎖

另外,如果我們使用的是INSERT ... ON DUPLICATE KEY ...這樣的語法來插入記錄時,如果遇到主鍵或者唯一二級索引列值重複的情況,會對B+樹中已存在的相同鍵值的記錄加X鎖。

  • 外鍵檢查

假設我們有一個父表和一個子表,我們需要在子表中插入一條記錄,如果待插入記錄的外鍵值能在父表中找到,不管哪種隔離級別,只需要直接給父表中相應的記錄加S鎖;如果待插入記錄的外鍵值在父表中找不到,在未提交讀/提交讀隔離級別下不會加鎖,但在可重複讀隔離級別下,會加間隙鎖。

聲明:本博客純粹爲讀書筆記,如想詳細瞭解MySQL相關知識請訪問《MySQL是怎麼運行的:從根兒上理解MySQL》原作者撰寫資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章