你瞭解幻讀嗎?

首先咱們建立一個表,並插入測試數據:

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

什麼是幻讀?

  1. 在可重複讀隔離級別下,普通的快照讀詩不會看到別的事務插入的數據的,所以,幻讀只在「當前讀」下才會出現
  2. 幻讀專指新插入的行。修改的數據被其餘事務的「當前讀」看到,不能稱爲幻讀。

幻讀有什麼問題?

語義:

  如圖: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也是在這個間隙進行加鎖,這兩個鎖的目的都是爲了保護這個間隙不被插入數據,因此他們之間是不衝突的。javascript

間隙鎖和行鎖合城 next-key lock。每一個next-key lock都是前開後閉區間。

間隙鎖帶來的問題:

例如:java

這是一個間隙鎖致使死鎖的問題。
    1. SessionA執行更新語句後,因爲id=9這一行數據不存在,因此會在(5,10)上加上間隙鎖。
    2. SessionB同理,兩個間隙鎖不會衝突。
    3. SessionB試圖插入一行數據(0,9,9),被SessionA的間隙鎖擋住,進入等待。
    4. SessionA試圖插入一行數據(0,9,9),被SessionB的間隙鎖擋住了。
    5. 循環等待,造成死鎖。
間隙鎖的引入,可能會致使一樣的語句鎖住更大的範圍,這實際上是影響了併發度的。
那麼有沒有簡單的方式,去解決幻讀呢?
能夠把數據庫的隔離級別設置爲讀提交,再將bin log的格式設置爲row,這樣能夠解決數據和日誌不一致的問題。若是咱們的業務需求,用讀提交就夠了,不用可重複度。能夠採用這種方法進行配置。
相關文章
相關標籤/搜索