解析InnoDB鎖類型及使用方式

為了提高InnoDB存儲(chǔ)引擎的并發(fā)性,InnoDB采用了細(xì)粒度的行鎖燃领,并在針對不同的鎖使用不同級別鎖或不使用鎖棕孙。本文將介紹InnoDB中使用的七種鎖,分別是共享鎖(share书蚪,S鎖)/排它鎖(exclusive,X鎖)迅栅、意向鎖殊校、記錄鎖、間隙鎖读存、臨鍵鎖为流、插入意向鎖呕屎、自增鎖。

1. 共享鎖(S鎖)/排它鎖(X鎖)(Share Lock/Exclusive Lock)

根據(jù)字面意思敬察,不難理解:
共享鎖秀睛,是不同的事務(wù)會(huì)話可以共享使用的鎖。排它鎖莲祸,是一種只能被某個(gè)會(huì)話獨(dú)享使用的鎖蹂安。
對于不同的會(huì)話A和B,如果A已經(jīng)獲取了某條數(shù)據(jù)R上的共享鎖锐帜,B也可以獲取R上的共享鎖田盈,但是不能獲取R上排它鎖。此種情況下抹估,B必須等待A釋放共享鎖后缠黍,才能獲取排它鎖。
如果A已經(jīng)獲取了某條數(shù)據(jù)R上的排它鎖药蜻,那么B既不能獲取R的共享鎖和R的排它鎖瓷式,必須等待A釋放數(shù)據(jù)R上的排它鎖;

等待语泽,具體表現(xiàn)為阻塞

鎖之間的兼容性如下表:

鎖類型 X S
X Conflict Conflict
S Conflict Compatible

注意:此處的鎖既可以表鎖贸典,也可以是行鎖
從上表可以看到排它鎖與其他的鎖都互不兼容,共享鎖之間是互相兼容踱卵。
那么如何才能獲取數(shù)據(jù)R上共享鎖or排它鎖呢廊驼?

#表定義
CREATE TABLE `t` (
`id`  int NOT NULL ,
`code`  int NOT NULL ,
PRIMARY KEY (`id`)
);

select id from t where id = 1 for update;  # 獲取id=1數(shù)據(jù)行的排它鎖

select id from t where id =1 lock in share mode;  # 獲取id=1數(shù)據(jù)行上的共享鎖
2.意向鎖(Intention Lock)

InnoDB支持多粒度的鎖( multiple granularity locking),允許表鎖和行鎖同時(shí)存在惋砂。
InnoDB通過意向鎖實(shí)現(xiàn)了多粒度的鎖妒挎。
意向鎖是表級別的鎖,表示了該會(huì)話在之后會(huì)使用的鎖(共享鎖or排它鎖)
意向鎖有兩種:共享意向鎖(IS)西饵、排它意向鎖(XS)

  1. 共享意向鎖表示一個(gè)事務(wù)即將需要獲得該表中某些行的共享鎖酝掩;
select lock in share mode; #可以獲得IS鎖
  1. 排它意向鎖表示一個(gè)事務(wù)即將需要獲得該表中某些行的排它鎖;
select ... for update;  # 獲得XS鎖

意向鎖協(xié)議規(guī)定如下:

  • 在一個(gè)事務(wù)獲得某條(些)數(shù)據(jù)行R上的共享鎖之前眷柔,該事務(wù)必須要獲得該表上的共享意向鎖(IS)期虾;
  • 在一個(gè)事務(wù)獲得某條(些)數(shù)據(jù)行R上的排它鎖之前,該事務(wù)必須要獲得該表上的排它意向鎖(XS)驯嘱;

S鎖镶苞,X鎖,IS鎖鞠评,IX鎖兼容性如下表所示:
注意:以下鎖均指的是表級別的鎖茂蚓,而非行級別的鎖。

鎖類型 X S IX IS
X Conflict Conflict Conflict Conflict
S Conflict Compatible Conflict Compatible
IX Conflict Conflict Compatible Compatible
IS Conflict Compatible Compatible Compatible

對于IX和X的組合,如果一個(gè)事務(wù)獲得表的X鎖煌贴,那么另一個(gè)事務(wù)如果想獲得IX時(shí)就會(huì)被阻塞御板,等待X鎖的釋放锥忿。
意向鎖僅僅表明意向牛郑,所以意向鎖不會(huì)阻塞除掃描全表之外的(比如"LOCK TABLES ... WRITE")。

3.記錄鎖(Record lock)

記錄鎖是一種加在索引數(shù)據(jù)記錄上鎖敬鬓,以防止其他事務(wù)對于該數(shù)據(jù)記錄進(jìn)行修改淹朋、刪除。
例如:

select id from t where id = 1 for update; # 當(dāng)前事務(wù)將獲得id=1這行記錄的排它記錄鎖
select id from t where id = 1 lock in share mode; # 當(dāng)前事務(wù)將獲得id=1這行記錄的共享鎖

通過update 钉答、delete操作數(shù)據(jù)時(shí)础芍,也會(huì)相應(yīng)的獲取對應(yīng)索引記錄的記錄鎖;

注意id為表t的主鍵数尿,innoDB默認(rèn)為主鍵加上了聚集索引(唯一性索引)仑性;

4.間隙鎖

間隙鎖是加載某個(gè)索引記錄區(qū)間上的鎖,間隙鎖防止其他事務(wù)在鎖定區(qū)間上插入新的記錄右蹦,避免不可重復(fù)讀诊杆。

那么如何獲取間隙鎖呢?
首先何陆,我們?yōu)楸韙插入幾條數(shù)據(jù)記錄:

mysql> insert into t value (1,1);
Query OK, 1 row affected (0.00 sec)

mysql> insert into t value (8,8);
Query OK, 1 row affected (0.00 sec)

mysql> insert into t value (20,20);
Query OK, 1 row affected (0.00 sec)

現(xiàn)在數(shù)據(jù)庫里面有3條數(shù)據(jù)晨汹,索引值為1,8,20。InnoDB會(huì)將索引id的區(qū)間進(jìn)行劃分為:
(-infinite贷盲,1)淘这,
(1,8)巩剖,
(8,20)铝穷,
(20,+infinite)四個(gè)區(qū)間佳魔;

例1 :
#事務(wù)會(huì)話A 曙聂,where 中  1 和 8 都是 索引值
select id from t where id between 1 and 8 lock in share mode; #獲取區(qū)間(1,8)上的共享間隙鎖;

其他事務(wù)如果想在(1,8)區(qū)間內(nèi)插入新的數(shù)據(jù)記錄吃引,將會(huì)阻塞直到超時(shí),如下所示:

#事務(wù)會(huì)話B
mysql> insert into t value (7,"test");
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction 
例2:
#事務(wù)A獲取間隙鎖筹陵,此時(shí)where中的4和14都不是實(shí)際存在的索引值
mysql> select id from t where id between 4 and 14 lock in share mode;
+----+
| id |
+----+
|  8 |
+----+
1 row in set (0.00 sec)
mysql> insert into t value (-1,"test");
Query OK, 1 row affected (0.00 sec)
mysql> insert into t value(2,"test");
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into t value (7,"test");
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into t value (10,"test");
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into t value(21,"test");
Query OK, 1 row affected (0.00 sec)

通過實(shí)驗(yàn),我們看到(1,8)镊尺,(8,20)這兩個(gè)區(qū)間被鎖住了朦佩,禁止了其他事務(wù)在這兩個(gè)區(qū)間中插入新的數(shù)據(jù)。
間隙鎖庐氮,一個(gè)非常關(guān)鍵的點(diǎn)就是如何確定鎖定區(qū)間范圍语稠?
InnoDB是通過對于where中范圍查找中的最小值sMin和最大值sMax,其中鎖定區(qū)間最小值lMin和最大值lMax。

  • lMin的值為表索引值中小于or等于sMin中的最大值仙畦,對于sMin=4的情況输涕,lMin=1。
  • lMax的值為表索引值中大于or等于sMax中的最小值慨畸,對于sMax=14的情況下莱坎,lMax=20.
    所以例2中區(qū)間的范圍是(1,20)也即(1,8)和(8,20)。

注意新增加code不是唯一性索引

5. 臨鍵鎖(next-key lock)

臨鍵鎖是記錄鎖和間隔鎖的結(jié)合寸士,它既可以鎖定索引記錄也可以鎖定索引記錄之間的區(qū)間檐什。臨鍵鎖主要的目的是解決幻讀的問題。
在隔離級別是可重復(fù)性讀(RR)下弱卡,并且innodb_locks_unsafe_for_binlog的值為OFF時(shí)乃正,InnoDB默認(rèn)使用的就是使用臨鍵鎖(next-key lock)。
在使用臨鍵鎖是婶博,如果查詢條件中含有唯一性索引瓮具,InnoDB會(huì)進(jìn)行鎖降級,變?yōu)橛涗涙i凡人。形式如:select id from t where id =1 for update名党;
如果查詢條件不是唯一性索引,那么SQL會(huì)獲得什么鎖呢划栓?

# 修改表t的結(jié)構(gòu)兑巾,為code增加索引,該索引是普通索引(Secondary Index)并且非唯一性索引(Unique Index)忠荞,并增加數(shù)據(jù)記錄(15,8)
mysql> ALTER TABLE `t`
ADD INDEX `idx_code` (`code`) ;
mysql> insert into t value(15,8);

修改后蒋歌,數(shù)據(jù)表的記錄為:
(1,1)
(8委煤,8)
(15堂油,8)
(20,20)
從而對于索引 idx_code碧绞,我們可以得到其對應(yīng)的區(qū)間分別為:
(-infinite府框,1],(1,8],(8,20],(20,+infinite)。
此時(shí)我們使用code為查詢條件進(jìn)行查詢:
事務(wù)A:

mysql> select id from t where code = 8 lock in share mode;
+----+
| id |
+----+
|  8 |
| 15 |
+----+
2 rows in set (0.00 sec)

對于事務(wù)A讥邻,當(dāng)執(zhí)行完加鎖讀的語句后迫靖,InnoDB會(huì)使用next-key進(jìn)行加鎖,由于code不是唯一性索引兴使,所以InnoDB不會(huì)對鎖進(jìn)行降級系宜。由于表中存在兩個(gè)索引,所以會(huì)分別進(jìn)行加鎖发魄。對于聚集索引列id盹牧,由于加鎖讀命中了兩條俩垃,id=8 和 id=15的記錄會(huì)加鎖。同時(shí)也會(huì)使用間隙鎖汰寓,對code對應(yīng)區(qū)間中的(1,8)和(8,20)進(jìn)行加鎖口柳。
現(xiàn)在通過事務(wù)B對以上結(jié)論進(jìn)行驗(yàn)證。
事務(wù)B:

mysql> insert into t value (-1,-1);
Query OK, 1 row affected (0.00 sec)
mysql> insert into t value (2,2);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into t value (9,9);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into t value (21,21);
Query OK, 1 row affected (0.00 sec)
mysql> select * from t where id = 8 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> select * from t where id = 15 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

通過事務(wù)B的insert和加鎖讀語句有滑,驗(yàn)證事務(wù)A獲得鎖跃闹。
在進(jìn)一步討論這個(gè)問題,如果code列沒有索引俺孙,那么將會(huì)加什么鎖呢辣卒?
事務(wù)A:

mysql> select id from t where code = 8 lock in share mode;
+----+
| id |
+----+
|  8 |
| 15 |
+----+
2 rows in set (0.00 sec)

由于code沒有索引掷贾,為了找到code=8的數(shù)據(jù)記錄睛榄,InnoDB將會(huì)進(jìn)行全表掃描,此時(shí)全部的數(shù)據(jù)記錄將會(huì)被鎖定想帅,其他事務(wù)無法進(jìn)行操作场靴。
接下來利用事務(wù)B進(jìn)行驗(yàn)證:
事務(wù)B:

mysql> insert into t value (-1,-1);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into t value (2,2);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into t value (9,9);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into t value (21,21);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> select * from t where id = 8 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> select * from t where id = 15 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> select * from t where id = 1 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> select * from t where id = 20 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

可以看到表t所有間隙都被鎖定,驗(yàn)證上述分析結(jié)論港准。

6. 插入意向鎖(Insert Intention )

InnoDB為了提高數(shù)據(jù)插入并發(fā)的效率旨剥,引入了插入意向鎖。
插入意向鎖是一種間隙鎖浅缸,當(dāng)事務(wù)視圖想向一個(gè)區(qū)間插入數(shù)據(jù)時(shí)轨帜,首先獲得插入意向鎖。
插入間隙鎖規(guī)定衩椒,如果多個(gè)事務(wù)向同一區(qū)間插入數(shù)據(jù)時(shí)蚌父,如果這些數(shù)據(jù)屬于不同的位置(索引位置),那么之間的插入是互不影響的毛萌。

This lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap.

7. 自增鎖(AUTO-INC lock)

自增鎖是一種特殊表鎖苟弛,這種鎖用在對有自增列的表進(jìn)行insert時(shí)。當(dāng)事務(wù)A向自增表插入數(shù)據(jù)表阁将,那么事務(wù)A將獲得該表上自增鎖膏秫,其他事務(wù)試圖想該表插入數(shù)據(jù)時(shí),必須等待事務(wù)釋放該表的自增鎖做盅。

總結(jié):

InnnoDB中鎖的使用缤削,與查詢是否使用索引(全表掃描or not)、索引類型(聚集索引or普通索引)吹榴、查詢范圍(單個(gè)or區(qū)間)都有關(guān)系亭敢。但是掌握了各個(gè)鎖的使用情況,就能根據(jù)具體的SQL判斷出使用鎖的類型腊尚。

參考:
  1. https://dev.mysql.com/doc/refman/5.6/en/innodb-storage-engine.html
  2. MySQL技術(shù)內(nèi)幕-InnoDB存儲(chǔ)引擎
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吨拗,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌劝篷,老刑警劉巖哨鸭,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異娇妓,居然都是意外死亡像鸡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門哈恰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來只估,“玉大人,你說我怎么就攤上這事着绷』赘疲” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵荠医,是天一觀的道長吁脱。 經(jīng)常有香客問我,道長彬向,這世上最難降的妖魔是什么兼贡? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮娃胆,結(jié)果婚禮上遍希,老公的妹妹穿的比我還像新娘。我一直安慰自己里烦,他們只是感情好凿蒜,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著招驴,像睡著了一般篙程。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上别厘,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天虱饿,我揣著相機(jī)與錄音,去河邊找鬼触趴。 笑死氮发,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的冗懦。 我是一名探鬼主播爽冕,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼披蕉!你這毒婦竟也來了颈畸?” 一聲冷哼從身側(cè)響起乌奇,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎眯娱,沒想到半個(gè)月后礁苗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡徙缴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年试伙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片于样。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡疏叨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出穿剖,到底是詐尸還是另有隱情蚤蔓,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布携御,位于F島的核電站昌粤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏啄刹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一凄贩、第九天 我趴在偏房一處隱蔽的房頂上張望誓军。 院中可真熱鬧,春花似錦疲扎、人聲如沸昵时。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽壹甥。三九已至,卻和暖如春壶熏,著一層夾襖步出監(jiān)牢的瞬間句柠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工棒假, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留溯职,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓帽哑,卻偏偏與公主長得像谜酒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子妻枕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容