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