理清MySQL的行鎖、意向鎖、記錄鎖、間隙鎖和臨鍵鎖
在日常開發(fā)工作中,Mysql是常用的數(shù)據(jù)庫之一,突然某天Mysql數(shù)據(jù)庫告警提示出現(xiàn)了死鎖問題,為了解決死鎖問題,我們就需要掌握一些關(guān)于Mysql的鎖的知識(shí)。
1、行鎖
在InnoDB存儲(chǔ)引擎中行級(jí)鎖每次操作鎖住對(duì)應(yīng)的行數(shù)據(jù),鎖定粒度最小,發(fā)生鎖沖突的概率最低,并發(fā)度最高。InnoDB的數(shù)據(jù)是基于索引組織的,行鎖是通過對(duì)索引上的索引項(xiàng)加鎖來實(shí)現(xiàn)的,而不是對(duì)記錄加的鎖。在InnoDB存儲(chǔ)引擎下實(shí)現(xiàn)了共享鎖和排他鎖這兩種行鎖,以下是兩種鎖的介紹:
(1)共享鎖(簡(jiǎn)稱:S)
允許一個(gè)事務(wù)去讀一行,阻止其他事務(wù)獲得相同數(shù)據(jù)集的排它鎖。(加了共享鎖之后可以讀取,但是不可以寫) ,典型是在查詢后面添加for share。在Mysql的performance_schema下的data_locks表中記錄關(guān)于鎖的相關(guān)信息,記錄鎖信息的表位置所示的:
圖片
執(zhí)行如下的sql語句:
BEGIN;
#共享鎖
SELECT * from stock where id = 8 FOR SHARE;
查詢data_locks表的鎖信息:
圖片
S,REC_NOT_GAP:表示對(duì)id=8的數(shù)據(jù)添加一把讀鎖(S),其中REC_NOT_GAP表示鎖的一個(gè)范圍(是指到底去鎖哪些數(shù)據(jù)),這里表示只鎖住id=8的數(shù)據(jù)。
(2)排他鎖(簡(jiǎn)稱:X)
允許獲取排他鎖的事務(wù)更新數(shù)據(jù),阻止其他事務(wù)獲得相同數(shù)據(jù)集的共享鎖和排他鎖。(加了寫鎖之后其他的事務(wù)不可以添加任何的鎖【讀鎖、寫鎖都不可以】)默認(rèn)每次insert、update、delete的時(shí)候都是加排他鎖,如下的更新sql:
BEGIN;
#排他鎖
update stock set num= 81 where id = 8;
查詢data_locks表的鎖信息:
圖片
X,REC_NOT_GAP:表示對(duì)id=8的數(shù)據(jù)添加一把排他鎖(X),同樣的REC_NOT_GAP表示鎖的一個(gè)范圍。
如果對(duì)select查詢添加for update的時(shí)候,此時(shí)就是排他鎖,如下的sql:
BEGIN;
#排他鎖
SELECT * from stock where id = 8 FOR UPDATE;
查詢data_locks表的鎖信息:
圖片
排他鎖和共享鎖的兼容性如下鎖整理:
圖片
在案例中我們使用的是主鍵id做為where的查詢條件,假設(shè)我們現(xiàn)在不使用id而是使用一個(gè)非索引字段作作為查詢的條件,sql如下所示:
BEGIN;
#共享鎖
SELECT * from stock where name = 'A' FOR SHARE;
數(shù)據(jù)表中的現(xiàn)存的記錄如下所示:
圖片
執(zhí)行sql后查詢data_locks表的鎖信息:
圖片
我們可以發(fā)現(xiàn)目前鎖類型就是表鎖了。
2、記錄鎖
鎖一條真實(shí)存在的記錄(數(shù)據(jù)庫中真實(shí)存在的數(shù)據(jù)),如下圖是數(shù)據(jù)表中的數(shù)據(jù)記錄:
圖片
通過sql查詢id=8的記錄,sql如下所示:
BEGIN;
#共享鎖
SELECT * from stock where id = 8 FOR SHARE;
鎖的結(jié)果:
圖片
3、間隙鎖
間隙是指索引跟索引之間的間隙,假設(shè)現(xiàn)在查詢id=5的數(shù)據(jù)(數(shù)據(jù)庫中id為5的數(shù)據(jù)不存在),如下的數(shù)據(jù)表數(shù)據(jù):
圖片
執(zhí)行如下的sql:
BEGIN;
#共享鎖
SELECT * from stock where id = 5 FOR SHARE;
查詢data_locks表的鎖信息:
圖片
S表示的讀鎖,GAP表示的間隙的意思,8代表的是一個(gè)節(jié)點(diǎn)(真實(shí)的記錄),這里的含義是1-8之間的間隙是鎖住的,這個(gè)間隙之內(nèi)不可以添加數(shù)據(jù),但是可以修改數(shù)據(jù)。
4、臨鍵鎖
臨鍵鎖是記錄鎖+間隙鎖,因?yàn)樵谌ゼ渔i來鎖數(shù)據(jù)的時(shí)候,那么可能既包含了區(qū)間也包含了一條真實(shí)的數(shù)據(jù),假設(shè)數(shù)據(jù)表中的數(shù)據(jù)如下所示:
圖片
現(xiàn)在執(zhí)行sql:
BEGIN;
#共享鎖
SELECT * from stock where id > 5 and id < 14 FOR SHARE;
查詢data_locks表的鎖信息:
圖片
id=8這條數(shù)據(jù)的LOCK_MODE=S,它沒有任何的標(biāo)記,那么id=8這條數(shù)據(jù)就是臨鍵鎖(臨鍵鎖只標(biāo)記了是X還是S);它表示既鎖死了id=8這條數(shù)據(jù),也鎖死了id在1-8這個(gè)區(qū)間。
id=14這條數(shù)據(jù)中,它沒有鎖死id=14這個(gè)數(shù)據(jù),只鎖死了一個(gè)gap的區(qū)間。
5、意向鎖
意向鎖是為了提高粗粒度鎖的性能而設(shè)置的一種預(yù)判機(jī)制(意向鎖是為了協(xié)調(diào)行鎖和表鎖的關(guān)系,用于優(yōu)化InnoDB加鎖的策略),意向鎖的主要作用是避免為了判斷表是否存在行鎖而去全表掃描(即在一個(gè)操作發(fā)起實(shí)際資源的鎖申請(qǐng)行為之前,先對(duì)更粗力度的資源發(fā)起一個(gè)加鎖意向聲明),意向鎖是由InnoDB在操作數(shù)據(jù)之前自動(dòng)加的,不需要用戶干預(yù)。如下所示的意向鎖:
圖片
意向鎖分為意向共享鎖(IS鎖)【事務(wù)在請(qǐng)求S鎖前,要先獲得IS鎖】;意向排他鎖(IX鎖)【事務(wù)在請(qǐng)求X鎖前,要先獲得IX鎖】
意向鎖(IS/IX)和X鎖是沖突的,如下所示事務(wù)A執(zhí)行語句:
BEGIN;
#共享鎖
SELECT * from stock where id = 8 FOR SHARE;
事務(wù)B的執(zhí)行語句:
BEGIN;
#排他鎖
update stock set num= 140 where id = 14;
執(zhí)行的效果圖如下所示:
圖片
①事務(wù)A首先申請(qǐng)整個(gè)表的IS鎖(成功)。
圖片
②事務(wù)A申請(qǐng)id=8這一行的S鎖(成功)。
圖片
③事務(wù)B申請(qǐng)整個(gè)表的IX鎖(成功);因?yàn)镮S和IX鎖是兼容的,并且IX鎖和行級(jí)別的S鎖也是兼容的。
圖片
④事務(wù)B申請(qǐng)整個(gè)表的X鎖(成功);
圖片
所以整個(gè)過程的數(shù)據(jù)庫鎖的信息:
圖片
如果現(xiàn)在事務(wù)A給行記錄id=8加共享鎖成功后,事務(wù)B給id=8的行記錄加排他鎖,此時(shí)事務(wù)B就需要等待事務(wù)A釋放鎖才能加鎖成功,如下圖所示:
圖片
數(shù)據(jù)庫的鎖信息如下所示:
圖片
可以發(fā)現(xiàn)事務(wù)B此時(shí)在等待鎖。
意向鎖與其他鎖的兼容性如下表整理:
圖片
意向鎖是一種高效的鎖機(jī)制,特別適用于支持行級(jí)鎖的數(shù)據(jù)庫系統(tǒng),能夠在多事務(wù)并發(fā)訪問的環(huán)境下有效地管理鎖,提高系統(tǒng)的并發(fā)性和數(shù)據(jù)一致性。