InnoDB如何解決幻讀沛婴?

InnoDB如何解決幻讀狸剃?

一致性讀

一致性非鎖定讀(Consistent Nonlocking Reads)

一致性不鎖定讀(Consistent Nonlocking Reads)是InnoDB使用多版本控制(MVCC)來讀取某個時間點創(chuàng)建的快照的止。這個請求只能看到這個時間點之前提交的事物的修改声诸,

看不到之后的或者未提交的事務的修改赠幕。如下圖:


快照讀:讀取的是快照版本殖妇,也就是歷史版本买猖。普通的SELECT就是快照讀

當前讀:讀取的是最新版本改橘。UPDATE、DELETE玉控、INSERT飞主、SELECT … LOCK IN SHARE MODE、SELECT … FOR UPDATE是當前讀高诺。

什么是幻讀碌识?

The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if aSELECTis executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row.

事務A 按照一定條件進行數(shù)據(jù)讀取, 期間事務B 插入了相同搜索條件的新數(shù)據(jù)虱而,事務A再次按照原先條件進行讀取時筏餐,發(fā)現(xiàn)了事務B 新插入的數(shù)據(jù) 稱為幻讀

the exception to this rule is that the query sees the changes made by earlier statements within the same transaction. This exception causes the following anomaly: If you update some rows in a table, aSELECTsees the latest version of the updated rows, but it might also see older versions of any rows. If other sessions simultaneously update the same table, the anomaly means that you might see the table in a state that never existed in the database.

按照規(guī)則,在同一個事務中修改的內(nèi)容牡拇,在之后的查詢中應該可以看到魁瞪。但是如果其他事務對同樣的內(nèi)容做了修改,那之后的查詢就會看到本不應該看到的數(shù)據(jù)惠呼。


創(chuàng)建表导俘,并插入數(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);

?sessionAsessionB

T1begin;?

T2select * from t where d=5;? ####result: (5,5,5)?

T3?insert into t value(1,1,5)

T4update t set d=100 where d=5;?

T5select * from t where d=5;? ? ? ### result: (1,1,100) (5,5,100)?

因為sessionA中的每一條查詢語句都加了for update 排他鎖,所以都是當前讀剔蹋。讀到的都是數(shù)據(jù)庫中最新的數(shù)據(jù)旅薄。

T2時刻查詢d=5,查出來一條記錄(5,5,5)

T3時刻sessionC 插入了一條記錄(1,1,5)

T4時刻泣崩,sessionA更新了d=5的行

T5時刻少梁,再次查詢d=5洛口,得到兩個結(jié)果 (1,100,5) (5,100,5)

sessionA在一次事務中得到了兩個不同的結(jié)果,出現(xiàn)了幻讀

間隙鎖

間隙鎖顧名思義凯沪,鎖的是兩個索引記錄的間隙绍弟,或者是第一個索引記錄之前或者最后一個索引之后的間隙。

SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;

無論c1=15這個值是否在表中存在著洼,其他的事務都不可以對t.c1=15這個值進行插入操作樟遣。因為10到20這個間隙(gap)被加鎖了。

間隙鎖之間并不沖突身笤,一個事務在一個間隙加了間隙排他鎖豹悬,其他事務同樣可以在該間隙加排他鎖或者共享鎖

如果是唯一索引的等值查詢,間隙鎖會退化成行鎖


Next-Key Locks

next-key lock是行鎖和間隙鎖的結(jié)合液荸。

InnoDBperforms row-level locking in such a way that when it searches or scans a table index, it sets shared or exclusive locks on the index records it encounters. Thus, the row-level locks are actually index-record locks. A next-key lock on an index record also affects the “gap” before that index record. That is, a next-key lock is an index-record lock plus a gap lock on the gap preceding the index record. If one session has a shared or exclusive lock on recordRin an index, another session cannot insert a new index record in the gap immediately beforeRin the index order.

加鎖規(guī)則:

next-key lock 是前開后閉區(qū)間瞻佛。

查找過程中訪問到的對象才會加鎖。

索引上的等值查詢娇钱,給唯一索引加鎖的時候伤柄,next-key lock 退化為行鎖。

索引上的等值查詢文搂,向右遍歷時且最后一個值不滿足等值條件的時候适刀,next-key lock 退化為間隙鎖。

唯一索引上的范圍查詢會訪問到不滿足條件的第一個值為止煤蹭。

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);

場景一:等值查詢的間隙鎖

由于表 t 中沒有 id=7 的記錄

加鎖單位是 next-key lock笔喉,session A 加鎖范圍就是 (5,10];

這是一個等值查詢 (id=7)硝皂,而 id=10 不滿足查詢條件常挚,next-key lock 退化成間隙鎖,因此最終加鎖的范圍是 (5,10)稽物。所以奄毡,session B 要往這個間隙里面插入 id=8 的記錄會被鎖住,但是 session C 修改 id=10 這行是可以的贝或。

場景二:非唯一索引等值鎖

這里 session A 要給索引 c 上 c=5 的這一行加上讀鎖吼过。

加鎖單位是 next-key lock,因此會給 (0,5]加上 next-key lock傀缩。

c 是普通索引那先,因此僅訪問 c=5 這一條記錄是不能馬上停下來的,需要向右遍歷赡艰,查到 c=10 才放棄售淡。訪問到的都要加鎖,因此要給 (5,10]加 next-key lock。

同時這個符合: 等值判斷揖闸,向右遍歷揍堕,最后一個值不滿足 c=5 這個等值條件,因此退化成間隙鎖 (5,10)汤纸。

只有訪問到的對象才會加鎖衩茸,這個查詢使用覆蓋索引,并不需要訪問主鍵索引贮泞,所以主鍵索引上沒有加任何鎖楞慈,這就是為什么 session B 的 update 語句可以執(zhí)行完成.。

lock in share mode 只鎖覆蓋索引啃擦,但是如果是 for update 就不一樣了囊蓝。 執(zhí)行 for update 時,系統(tǒng)會認為你接下來要更新數(shù)據(jù)令蛉,因此會順便給主鍵索引上滿足條件的行加上行鎖聚霜。

場景三:主鍵索引范圍鎖

開始執(zhí)行的時候,要找到第一個 id=10 的行珠叔,因此本該是 next-key lock(5,10]蝎宇。? 主鍵 id 上的等值條件,退化成行鎖祷安,只加了 id=10 這一行的行鎖姥芥。

范圍查找就往后繼續(xù)找,找到 id=15 這一行停下來辆憔,因此需要加 next-key lock(10,15]撇眯。

場景四:非唯一索引范圍鎖

在第一次用 c=10 定位記錄的時候报嵌,索引 c 上加了 (5,10]這個 next-key lock 虱咧。

由于索引 c 是非唯一索引,也就是說不會蛻變?yōu)樾墟i锚国,因此最終 sesion A 加的鎖是腕巡,索引 c 上的 (5,10] 和 (10,15] 這兩個 next-key lock。

場景五:唯一索引范圍鎖 bug

session A 是一個范圍查詢血筑,按照原則 1 的話绘沉,應該是索引 id 上只加 (10,15]這個 next-key lock,并且因為 id 是唯一鍵豺总,所以循環(huán)判斷到 id=15 這一行就應該停止了车伞。

但是實現(xiàn)上,InnoDB 會往前掃描到第一個不滿足條件的行為止喻喳,也就是 id=20另玖。而且由于這是個范圍掃描,因此索引 id 上的 (15,20]這個 next-key lock 也會被鎖上。所以你看到了谦去,session B 要更新 id=20 這一行慷丽,是會被鎖住的。同樣地鳄哭,session C 要插入 id=16 的一行要糊,也會被鎖住。照理說妆丘,這里鎖住 id=20 這一行的行為锄俄,其實是沒有必要的。因為掃描到 id=15勺拣,就可以確定不用往后再找了珊膜。但實現(xiàn)上還是這么做了。

場景六:非唯一索引上存在"等值"的例子

給表插入一條新的記錄

mysql> insert into t values(30,10,30);

表里現(xiàn)在有兩條c=10的記錄宣脉。但是因為這兩行的主鍵id不同车柠,因此,這兩個c=10的記錄之間塑猖,也是有間隙的

session A 在遍歷的時候竹祷,先訪問第一個 c=10 的記錄。同樣地羊苟,這里加的是 (c=5,id=5) 到 (c=10,id=10) 這個 next-key lock塑陵。

session A 向右查找,直到碰到 (c=15,id=15) 這一行蜡励,循環(huán)才結(jié)束令花。這是一個等值查詢,向右查找到了不滿足條件的行凉倚,所以會退化成 (c=10,id=10) 到 (c=15,id=15) 的間隙鎖兼都。


場景七:limit 語句加鎖


session A 的 delete 語句加了 limit 2。你知道表 t 里 c=10 的記錄其實只有兩條稽寒,因此加不加 limit 2扮碧,刪除的效果都是一樣的,但是加鎖的效果卻不同杏糙∩魍酰可以看到,session B 的 insert 語句執(zhí)行通過了宏侍,跟場景六的結(jié)果不同赖淤。

這是因為,這里里的 delete 語句明確加了 limit 2 的限制谅河,因此在遍歷到 (c=10, id=30) 這一行之后咱旱,滿足條件的語句已經(jīng)有兩條嗜愈,循環(huán)就結(jié)束了。

索引 c 上的加鎖范圍就變成了從(c=5,id=5) 到(c=10,id=30) 這個前開后閉區(qū)間

在刪除數(shù)據(jù)的時候盡量加 limit莽龟。這樣不僅可以控制刪除數(shù)據(jù)的條數(shù)蠕嫁,讓操作更安全,還可以減小加鎖的范圍毯盈。


場景八:一個死鎖的例子

session A 啟動事務后執(zhí)行查詢語句加 lock in share mode剃毒,在索引 c 上加了 next-key lock(5,10] 和間隙鎖 (10,15);

session B 的 update 語句也要在索引 c 上加 next-key lock(5,10] 搂赋,進入鎖等待赘阀;

然后 session A 要再插入 (8,8,8) 這一行,被 session B 的間隙鎖鎖住脑奠。由于出現(xiàn)了死鎖基公,InnoDB 讓 session B 回滾。

session B 的“加 next-key lock(5,10] ”操作宋欺,實際上分成了兩步轰豆,先是加 (5,10) 的間隙鎖,加鎖成功齿诞;然后加 c=10 的行鎖酸休,這時候才被鎖住的。


在RR的隔離級別下祷杈,Innodb使用MVCC和next-key locks解決幻讀斑司,MVCC解決的是普通讀(快照讀)的幻讀,next-key locks解決的是當前讀情況下的幻讀但汞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宿刮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子私蕾,更是在濱河造成了極大的恐慌僵缺,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件是目,死亡現(xiàn)場離奇詭異谤饭,居然都是意外死亡,警方通過查閱死者的電腦和手機懊纳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來亡容,“玉大人嗤疯,你說我怎么就攤上這事」刖ぃ” “怎么了茂缚?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵戏罢,是天一觀的道長。 經(jīng)常有香客問我脚囊,道長龟糕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任悔耘,我火速辦了婚禮讲岁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘衬以。我一直安慰自己缓艳,他們只是感情好,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布看峻。 她就那樣靜靜地躺著阶淘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪互妓。 梳的紋絲不亂的頭發(fā)上溪窒,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音冯勉,去河邊找鬼霉猛。 笑死,一個胖子當著我的面吹牛珠闰,可吹牛的內(nèi)容都是我干的惜浅。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼伏嗜,長吁一口氣:“原來是場噩夢啊……” “哼坛悉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起承绸,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤裸影,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后军熏,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體轩猩,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年荡澎,在試婚紗的時候發(fā)現(xiàn)自己被綠了均践。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡摩幔,死狀恐怖彤委,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情或衡,我是刑警寧澤焦影,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布车遂,位于F島的核電站,受9級特大地震影響斯辰,放射性物質(zhì)發(fā)生泄漏舶担。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一彬呻、第九天 我趴在偏房一處隱蔽的房頂上張望衣陶。 院中可真熱鬧,春花似錦废岂、人聲如沸祖搓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拯欧。三九已至,卻和暖如春财骨,著一層夾襖步出監(jiān)牢的瞬間镐作,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工隆箩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留该贾,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓捌臊,卻偏偏與公主長得像杨蛋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子理澎,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345