你了解幻读吗?
首先我們創(chuàng)建一個(gè)表,并插入測(cè)試數(shù)據(jù):
CREATE TABLE `t` ( `id` int(11) NOT NULL, `c` int(11) DEFAULT NULL, `d` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `c` (`c`) ) ENGINE=InnoDB; insert into t values(0,0,0),(5,5,5), (10,10,10),(15,15,15),(20,20,20),(25,25,25);
什么是幻讀?
在可重復(fù)讀隔離級(jí)別下,普通的快照讀詩(shī)不會(huì)看到別的事務(wù)插入的數(shù)據(jù)的,因此,幻讀只在“當(dāng)前讀”下才會(huì)出現(xiàn)
幻讀專指新插入的行。修改的數(shù)據(jù)被其他事務(wù)的“當(dāng)前讀”看到,不能稱為幻讀。
幻讀有什么問(wèn)題?
語(yǔ)義:
如圖:T1時(shí)刻, sessionA的語(yǔ)言是將所有d=5的數(shù)據(jù),加行鎖。T2時(shí)刻,SessionB將id=0這條數(shù)據(jù)改成了d=5,此時(shí)有兩條 d=5的數(shù)據(jù),但是由于第二條數(shù)據(jù)并沒(méi)有加鎖,可以對(duì)其進(jìn)行修改。這就破壞了sessionA里的Q1語(yǔ)句要鎖住所有d=5的行的加鎖聲明。
數(shù)據(jù)一致性:
數(shù)據(jù)一致性不僅指數(shù)據(jù)庫(kù)內(nèi)部數(shù)據(jù)狀態(tài)的一致性,還包含了數(shù)據(jù)和日志在邏輯上的一致性
T6時(shí)刻后,數(shù)據(jù)庫(kù)中的數(shù)據(jù)為:
(5,5,100)
(0,5,5)
(1,5,5)
而bin log里的內(nèi)容如下:
update t set d=5 where id=0; /*(0,0,5)*/ update t set c=5 where id=0; /*(0,5,5)*/ insert into t values(1,1,5); /*(1,1,5)*/ update t set c=5 where id=1; /*(1,5,5)*/ update t set d=100 where d=5;/* 所有 d=5 的行,d 改成 100*/
可以看到id=0和id=1這兩行,發(fā)生了數(shù)據(jù)不一致。
可以看到id=0和id=1這兩行,發(fā)生了數(shù)據(jù)不一致。
InnoDB如何解決幻讀問(wèn)題?
為了解決幻讀的問(wèn)題,InnoDB引入了新的鎖——間隙鎖(Gap Lock)
即將兩個(gè)值之間的空隙進(jìn)行加鎖,比如表t,初始化插入了6個(gè)記錄,這就會(huì)產(chǎn)生7個(gè)空隙
此時(shí),我們執(zhí)行
select * from t where d = 5 for update;
的時(shí)候,就不僅給這已有的6個(gè)記錄加上了行鎖,同時(shí)加了7個(gè)間隙鎖,這樣就保證了無(wú)法再插入新的記錄。
注意:
對(duì)于非索引字段進(jìn)行update或select..for update操作的時(shí)候,代價(jià)極高(上述SQL)。所有記錄上鎖,已經(jīng)所有間隙加鎖
對(duì)于索引字段執(zhí)行上述操作,只有字段本身一級(jí)附近的間隙會(huì)被加鎖。
間隙鎖存在沖突,是“往這個(gè)間隙中插入一個(gè)記錄”這個(gè)操作。
思考一下這是為什么呢?
InnoDB索引是一顆B+樹,同一級(jí)上的節(jié)點(diǎn)是順序排列的,所以只需要鎖住字段本身和附近間隙即可,因?yàn)楹蟛迦氲臄?shù)據(jù)位置是固定的。非索引字段則不具備這樣的結(jié)構(gòu),所以只能采用全表掃描的方式加鎖。
例如:
這里由于表中沒(méi)有c=7這條記錄,并且 c是索引字段,所以SessionA會(huì)在(5,10)這個(gè)間隙上加鎖。而SessionB也是在這個(gè)間隙進(jìn)行加鎖,這兩個(gè)鎖的目的都是為了保護(hù)這個(gè)間隙不被插入數(shù)據(jù),所以他們之間是不沖突的。
間隙鎖和行鎖合城 next-key lock。每個(gè)next-key lock都是前開后閉區(qū)間。
間隙鎖帶來(lái)的問(wèn)題:
例如:
這是一個(gè)間隙鎖導(dǎo)致死鎖的問(wèn)題。
SessionA執(zhí)行更新語(yǔ)句后,由于id=9這一行數(shù)據(jù)不存在,所以會(huì)在(5,10)上加上間隙鎖。
SessionB同理,兩個(gè)間隙鎖不會(huì)沖突。
SessionB試圖插入一行數(shù)據(jù)(0,9,9),被SessionA的間隙鎖擋住,進(jìn)入等待。
SessionA試圖插入一行數(shù)據(jù)(0,9,9),被SessionB的間隙鎖擋住了。
循環(huán)等待,形成死鎖。
間隙鎖的引入,可能會(huì)導(dǎo)致同樣的語(yǔ)句鎖住更大的范圍,這其實(shí)是影響了并發(fā)度的。
那么有沒(méi)有簡(jiǎn)單的方式,去解決幻讀呢?
可以把數(shù)據(jù)庫(kù)的隔離級(jí)別設(shè)置為讀提交,再將bin log的格式設(shè)置為row,這樣可以解決數(shù)據(jù)和日志不一致的問(wèn)題。如果我們的業(yè)務(wù)需求,用讀提交就夠了,不用可重復(fù)度。可以采用這種方法進(jìn)行配置。
總結(jié)
- 上一篇: 热带森林探险有哪些注意事项
- 下一篇: 无尽冬日竞技场挑战玩家方法