本篇內(nèi)容主要講解“innodb幻讀問題怎么解決”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“innodb幻讀問題怎么解決”吧!
創(chuàng)新互聯(lián)專注于城中企業(yè)網(wǎng)站建設(shè),響應(yīng)式網(wǎng)站開發(fā),商城網(wǎng)站建設(shè)。城中網(wǎng)站建設(shè)公司,為城中等地區(qū)提供建站服務(wù)。全流程定制網(wǎng)站建設(shè),專業(yè)設(shè)計(jì),全程項(xiàng)目跟蹤,創(chuàng)新互聯(lián)專業(yè)和態(tài)度為您提供的服務(wù)
CREATE TABLE `test_20` ( `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 test_20 values(0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25);
begin;select * from t where d=5 for update;commit;
這個(gè)語句會命中 d=5 的這一行,對應(yīng)的主鍵 id=5,因此在 select 語句執(zhí)行完成后,id=5 這一行會加一個(gè)寫鎖,而且由于兩階段鎖協(xié)議,這個(gè)寫鎖會在執(zhí)行 commit 語句的時(shí)候釋放。
如果只在 id=5 這一行加鎖,而其他行的不加鎖的話,會怎么樣?
在READ COMMITTED級別下可執(zhí)行
在REPEATABLE READ級別下,session B會阻塞
1)Q1 只返回 id=5 這一行;
2)在 T2 時(shí)刻,session B 把 id=0 這一行的 d 值改成了 5,因此 T3 時(shí)刻 Q2 查出來的是id=0 和 id=5 這兩行;
3)在 T4 時(shí)刻,session C 又插入一行(1,1,5),因此 T5 時(shí)刻 Q3 查出來的是 id=0、id=1和 id=5 的這三行。
其中,Q3 讀到 id=1 這一行的現(xiàn)象,被稱為“幻讀”。也就是說,幻讀指的是一個(gè)事務(wù)在前后兩次查詢同一個(gè)范圍的時(shí)候,后一次查詢看到了前一次查詢沒有看到的行。
說明:
1)在可重復(fù)讀隔離級別下,普通的查詢是快照讀,是不會看到別的事務(wù)插入的數(shù)據(jù)的。因此,幻讀在“當(dāng)前讀”下才會出現(xiàn)。
2)上面 session B 的修改結(jié)果,被 session A 之后的 select 語句用“當(dāng)前讀”看到,不能稱為幻讀?;米x僅專指“新插入的行”。
session A 在 T1 時(shí)刻就聲明了,“我要把所有 d=5 的行鎖住,不準(zhǔn)別的事務(wù)進(jìn)行讀寫操作”。而實(shí)際上,這個(gè)語義被破壞了。
鎖的設(shè)計(jì)是為了保證數(shù)據(jù)的一致性。而這個(gè)一致性,不止是數(shù)據(jù)庫內(nèi)部數(shù)據(jù)狀態(tài)在此刻的一致性,還包含了數(shù)據(jù)和日志在邏輯上的一致性。
候 binlog 里面的內(nèi)容:
1) T2 時(shí)刻,session B 事務(wù)提交,寫入了兩條語句;
2)T4 時(shí)刻,session C 事務(wù)提交,寫入了兩條語句;
3)T6 時(shí)刻,session A 事務(wù)提交,寫入了 update t set d=100 where d=5 這條語句。
放在一起如下:
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*/
這個(gè)語句序列,不論是拿到備庫去執(zhí)行,還是以后用 binlog 來克隆一個(gè)庫,這三行的結(jié)果,都變成了 (0,5,100)、(1,5,100) 和 (5,5,100)。
原因是:
這是我們假設(shè)“select * from t where d=5 for update 這條語句只給d=5 這一行,也就是 id=5 的這一行加鎖”導(dǎo)致的。
進(jìn)一步假設(shè):
把掃描過程中碰到的行,也都加上寫鎖,再來看看執(zhí)行效果
binlog 里面執(zhí)行序列:
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*/update t set d=5 where id=0; /*(0,0,5)*/update t set c=5 where id=0; /*(0,5,5)*/
這里解決了session B的問題,但是session C新增的問題并沒有解決!
在 T3 時(shí)刻,我們給所有行加鎖的時(shí)候,id=1 這一行還不存在,不存在也就加不上鎖。也就是說,即使把所有的記錄都加上鎖,還是阻止不了新插入的記錄。
產(chǎn)生幻讀的原因是,行鎖只能鎖住行,但是新插入記錄這個(gè)動作,要更新的是記錄之間的“間隙”。因此,為了解決幻讀問題,InnoDB 只好引入新的鎖,也就是間隙鎖 (GapLock)。
間隙鎖,鎖的就是兩個(gè)值之間的空隙。比如表 test_20,初始化插入了 6 個(gè)記錄,這就產(chǎn)生了 7 個(gè)間隙。
當(dāng)你執(zhí)行 select * from t where d=5 for update 的時(shí)候,就不止是給數(shù)據(jù)庫中已有的 6個(gè)記錄加上了行鎖,還同時(shí)加了 7 個(gè)間隙鎖。這樣就確保了無法再插入新的記錄。
數(shù)據(jù)行是可以加上鎖的實(shí)體,數(shù)據(jù)行之間的間隙,也是可以加上鎖的實(shí)體。但是間隙鎖跟我們之前碰到過的鎖都不太一樣。
跟行鎖有沖突關(guān)系的是“另外一個(gè)行鎖”。
跟間隙鎖存在沖突關(guān)系的,是“往這個(gè)間隙中插入一個(gè)記錄”這個(gè)操作。間隙鎖之間都不存在沖突關(guān)系。
如下:這里 session B 并不會被堵住。因?yàn)楸?test_20里并沒有 c=7 這個(gè)記錄,因此 session A 加的是間隙鎖 (5,10)。而 session B 也是在這個(gè)間隙加的間隙鎖。它們有共同的目標(biāo),即:保護(hù)這個(gè)間隙,不允許插入值。但,它們之間是不沖突的。
間隙鎖和行鎖合稱 next-key lock,每個(gè) next-key lock 是前開后閉區(qū)間。也就是說,表test_20初始化以后,如果用 select * from t for update 要把整個(gè)表所有記錄鎖起來,就形成了 7 個(gè)next-key lock,分別是 (-∞,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20, 25]、(25,+suprenum]。
把間隙鎖記為開區(qū)間,把 next-keylock 記為前開后閉區(qū)間。
1)session A 執(zhí)行 select ... for update 語句,由于 id=9 這一行并不存在,因此會加上間隙鎖 (5,10);
2)session B 執(zhí)行 select ... for update 語句,同樣會加上間隙鎖 (5,10),間隙鎖之間不會沖突,因此這個(gè)語句可以執(zhí)行成功;
3)session B 試圖插入一行 (9,9,9),被 session A 的間隙鎖擋住了,只好進(jìn)入等待;
4)session A 試圖插入一行 (9,9,9),被 session B 的間隙鎖擋住了。
至此,兩個(gè) session 進(jìn)入互相等待狀態(tài),形成死鎖。間隙鎖的引入,可能會導(dǎo)致同樣的語句鎖住更大的范圍,這其實(shí)是影響了并發(fā)度的。
解決方法:
間隙鎖是在可重復(fù)讀隔離級別下才會生效的。所以,你如果把隔離級別設(shè)置為讀提交的話,就沒有間隙鎖了。但同時(shí),你要解決可能出現(xiàn)的數(shù)據(jù)和日志不一致問題,需要把 binlog 格式設(shè)置為 row。這,也是現(xiàn)在不少公司使用的配置組合。
到此,相信大家對“innodb幻讀問題怎么解決”有了更深的了解,不妨來實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!