MySQL InnoDB 鎖 學習筆記

所在文集:數據庫


本文的內容參考了:

下面會涉及到 MySQL 數據庫隔離級別和索引,請先參見:

自增鎖

MySQL InnoDB 默認的隔離級別為 RR,假設有數據表:
t(id AUTO_INCREMENT, name);
數據表中有數據:

1, shenjian
2, zhangsan
3, lisi

事務 A 先執行,還未提交:insert into t(name) values(xxx);
事務 B 后執行:insert into t(name) values(ooo);

問:事務B會不會被阻塞? 答案是會阻塞,分析如下:

  • 事務 A 先執行 insert,會得到一條 (4, xxx) 的記錄,由于是自增列,InnoDB 會自動增長,注意此時事務并未提交;
  • 事務 B 后執行 insert,假設不會被阻塞,那會得到一條 (5, ooo) 的記錄;

此時,并未有什么不妥,但如果,事務 A 繼續 insertinsert into t(name) values(xxoo);會得到一條 (6, xxoo) 的記錄。
事務 A 再 selectselect * from t where id>3; 得到的結果是:

4, xxx
6, xxoo

注意:不可能查詢到 (5, ooo) 的記錄,因為在 RR 的隔離級別下,不可能讀取到還未提交事務生成的數據。
這對于事務 A 來說,就很奇怪了,對于 AUTO_INCREMENT 的列,連續插入了兩條記錄,一條是 4,接下來一條變成了 6,就像莫名其妙的幻影。

自增鎖是一種特殊的表級別鎖(table-level lock),專門針對事務插入 AUTO_INCREMENT 類型的列。最簡單的情況,如果一個事務正在往表中插入記錄,所有其他事務的插入必須等待,以便第一個事務插入的行,是連續的主鍵值。

共享/排他鎖(Shared and Exclusive Locks)

共享鎖(S鎖)和排他鎖(X鎖)是行級別的鎖(row-level locking)

  • 事務拿到某一行記錄的共享S鎖,才可以讀取這一行;
    • 多個事務可以拿到一把共享S鎖,讀讀可以并行
  • 事務拿到某一行記錄的排它X鎖,才可以修改或者刪除這一行;
    • 只有一個事務可以拿到排它X鎖,寫寫/讀寫必須互斥;

共享/排它鎖的潛在問題是,不能充分的并行,解決思路是數據多版本。參見:MySQL InnoDB 并發控制,事務的實現 學習筆記

意向鎖(Intention Locks)

意向鎖是指,未來的某個時刻,事務可能要加共享/排它鎖了,先提前聲明一個意向。意向鎖有這樣一些特點:

  • 首先,意向鎖,是一個表級別的鎖(table-level locking)
  • 意向鎖分為:
    • 意向共享鎖(intention shared lock, IS)例如:select ... lock in share mode 要設置意向共享鎖;
    • 意向排它鎖(intention exclusive lock, IX)例如:select ... for update 要設置意向排它鎖;
  • 意向鎖協議(intention locking protocol)并不復雜:
    • 事務要獲得某些行的 S 鎖,必須先獲得表的 IS 鎖
    • 事務要獲得某些行的 X 鎖,必須先獲得表的 IX 鎖
  • 由于意向鎖僅僅表明意向,它其實是比較弱的鎖,意向鎖之間并不相互互斥,而是可以并行

插入意向鎖(Insert Intention Locks)

對已有數據行的修改 update 與刪除 delete,必須加排他鎖(X鎖),那對于數據的插入 insert,是否還需要加這么強的鎖,來實施互斥呢?

插入意向鎖,是間隙鎖(Gap Locks)的一種(所以,也是實施在索引上的),它是專門針對 insert 操作的:
多個事務,在同一個索引,同一個范圍區間插入記錄時,如果插入的位置不沖突,不會阻塞彼此。

回到上面自增鎖時所用的插入的例子。如果主鍵不是自增的 t(id unique PK, name); 數據表中有數據:

10, shenjian
20, zhangsan
30, lisi

事務 A 先執行,在 10 與 20 兩條記錄中插入了一行,還未提交:insert into t values(11, xxx);
事務 B 后執行,也在 10 與 20 兩條記錄中插入了一行:insert into t values(12, ooo);

問:會使用什么鎖?使用的是插入意向鎖。

問:事務B會不會被阻塞呢?雖然事務隔離級別是 RR,雖然是同一個索引,雖然是同一個區間,但插入的記錄并不沖突,故這里并不會阻塞事務 B。

思路總結:

  • InnoDB 使用共享鎖,可以提高讀讀并發;
  • 為了保證數據強一致,InnoDB 使用強互斥鎖,保證同一行記錄修改與刪除的串行性;
  • InnoDB 使用插入意向鎖,可以提高插入并發;

記錄鎖(Record Locks)

記錄鎖封鎖索引記錄,例如:
select * from t where id=1 for update;
它會在 id=1 的索引記錄上加鎖,以阻止其他事務插入,更新,刪除 id=1 的這一行。

需要說明的是:
select * from t where id=1;
則是快照讀(SnapShot Read),它并不加鎖。

間隙鎖(Gap Locks)

間隙鎖封鎖索引記錄中的間隔,或者第一條索引記錄之前的范圍,又或者最后一條索引記錄之后的范圍。
t(id PK, name KEY); 表中有四條記錄:

1, shenjian
3, zhangsan
5, lisi
9, wangwu

SQL 語句:select * from t where id between 8 and 15 for update;會封鎖區間,以阻止其他事務 id=10 的記錄插入。

為什么要阻止 id=10 的記錄插入?如果能夠插入成功,頭一個事務執行相同的 SQL 語句,會發現結果集多出了一條記錄,即幻影數據。(即我們采用 MySQL InnoDB 的 可重復讀 RR 隔離級別)

如果把事務的隔離級別降級為讀提交(Read Committed, RC),間隙鎖則會自動失效。

臨鍵鎖(Next-Key Locks)

臨鍵鎖,是記錄鎖與間隙鎖的組合,它的封鎖范圍,既包含索引記錄,又包含索引區間。
更具體的,臨鍵鎖會封鎖索引記錄本身,以及索引記錄之前的區間。
t(id PK, name KEY); 表中有四條記錄:

1, shenjian
3, zhangsan
5, lisi
9, wangwu

主鍵上潛在的臨鍵鎖為:

(-infinity, 1]
(1, 3]
(3, 5]
(5, 9]
(9, +infinity]

臨鍵鎖的主要目的,也是為了避免幻讀(Phantom Read)。如果把事務的隔離級別降級為RC,臨鍵鎖則也會失效。

各種SQL到底加了什么鎖

普通 select

  • 在讀未提交(Read Uncommitted),讀提交(Read Committed, RC),可重復讀(Repeated Read, RR)這三種事務隔離級別下,普通 selec 使用快照讀(snpashot read),不加鎖,并發非常高;
  • 在串行化(Serializable)這種事務的隔離級別下,普通 select 會升級為 select ... in share mode;

加鎖 select

  • select ... for update
  • select ... in share mode

如果,在唯一索引(unique index)上使用唯一的查詢條件(unique search condition),會使用記錄鎖(record lock),而不會封鎖記錄之間的間隔,即不會使用間隙鎖(gap lock)與臨鍵鎖(next-key lock);

假設有 InnoDB 表:t(id PK, name);

1, shenjian
2, zhangsan
3, lisi

SQL 語句:select * from t where id=1 for update; 只會封鎖記錄,而不會封鎖區間。
其他的查詢條件和索引條件,InnoDB 會封鎖被掃描的索引范圍,并使用間隙鎖與臨鍵鎖,避免索引范圍區間插入記錄。

update 與 delete

和加鎖 select 類似,如果在唯一索引上使用唯一的查詢條件來 update/delete,例如:
update t set name=xxx where id=1; 也只加記錄鎖;

否則,符合查詢條件的索引記錄之前,都會加排他臨鍵鎖(exclusive next-key lock),來封鎖索引記錄與之前的區間;

尤其需要特殊說明的是,如果 update 的是聚集索引(clustered index)記錄,則對應的普通索引(secondary index)記錄也會被隱式加鎖,這是由 InnoDB 索引的實現機制決定的:普通索引存儲 PK 的值,檢索普通索引本質上要二次掃描聚集索引。

insert

同樣是寫操作,insertupdatedelete 不同,它會用排它鎖封鎖被插入的索引記錄,而不會封鎖記錄之前的范圍。

同時,會在插入區間加插入意向鎖(insert intention lock),但這個并不會真正封鎖區間,也不會阻止相同區間的不同 KEY 插入。

InnoDB 調試死鎖

InnoDB 的行鎖都是實現在索引上的,實驗可以使用主鍵,建表時設定為 InnoDB 引擎:

create table t (
id int(10) primary key
)engine=innodb;

插入一些實驗數據:

start transaction;
insert into t values(1);
insert into t values(3);
insert into t values(10);
commit;

實驗一,間隙鎖互斥

開啟區間鎖,RR 的隔離級別下,上例會有四個區間:

(-infinity, 1)
(1, 3)
(3, 10)
(10, infinity)

事務 A 刪除某個區間內的一條不存在記錄,獲取到共享間隙鎖,會阻止其他事務 B 在相應的區間插入數據,因為插入需要獲取排他間隙鎖。

session A:

set session autocommit=0;
start transaction;
delete from t where id=5;

session B:

set session autocommit=0;
start transaction;
insert into t values(0);
insert into t values(2);
insert into t values(12);
insert into t values(7);

事務 B 插入的值:0, 2, 12 都不在 (3, 10) 區間內,能夠成功插入,而 7(3, 10) 這個區間內,會阻塞。

可以使用:show engine innodb status; 來查看鎖的情況。

如果事務 A 提交或者回滾,事務 B 就能夠獲得相應的鎖,以繼續執行。
如果事務 A 一直不提交,事務 B 會一直等待,直到超時。

實驗二,共享排他鎖死鎖

事務 A 先執行:

set session autocommit=0;
start transaction;
insert into t values(7);

事務 B 后執行:

set session autocommit=0;
start transaction;
insert into t values(7);

事務 C 最后執行:

set session autocommit=0;
start transaction;
insert into t values(7);

三個事務都試圖往表中插入一條為 7 的記錄:

  • A 先執行,插入成功,并獲取 id=7 的排他鎖;
  • B 后執行,需要進行PK校驗,故需要先獲取 id=7 的共享鎖,阻塞;
  • C 后執行,也需要進行PK校驗,也要先獲取 id=7 的共享鎖,也阻塞;

如果此時,事務 A 執行:rollback; 釋放 id=7排他鎖。
則 B,C 會繼續進行主鍵校驗:

  • B 會獲取到 id=7共享鎖,主鍵未互斥;
  • C 也會獲取到 id=7 共享鎖,主鍵未互斥;

B 和 C 要想插入成功,必須獲得 id=7 的排他鎖,但由于雙方都已經獲取到 id=7 的共享鎖,它們都無法獲取到彼此的排他鎖,死鎖就出現了。

當然,InnoDB有死鎖檢測機制,B 和 C 中的一個事務會插入成功,另一個事務會自動放棄。

共享排他鎖,在并發量插入相同記錄的情況下會出現,相應的案例比較容易分析。

實驗三,并發間隙鎖的死鎖

SQL 執行序列如下:

A:set session autocommit=0;
A:start transaction;
A:delete from t where id=6;
         B:set session autocommit=0;
         B:start transaction;
         B:delete from t where id=7;
A:insert into t values(5);
         B:insert into t values(8);
  • A 執行 delete 后,會獲得 (3, 10) 的共享間隙鎖。
  • B 執行 delete 后,也會獲得 (3, 10) 的共享間隙鎖。
  • A 執行 insert 后,希望獲得 (3, 10) 的排他間隙鎖,于是會阻塞。
  • B 執行 insert 后,也希望獲得 (3, 10) 的排他間隙鎖,于是死鎖出現。

檢測到死鎖后,事務 B 自動回滾了,事務 A 將會執行成功。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379