天天看點

《MySQL——幻讀與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);      

該表除了主鍵id,還有索引c。

問下面的語句序列,是怎麼加鎖的,加的鎖又是什麼時候釋放的呢?

begin;
select * from t where d = 5 for update;
commit;      

這條語句會命中d=5這一行,對應主鍵id=5,是以在select語句執行完成後,id=5這一行會加一個寫鎖,并且由于兩階段鎖協定,這個寫鎖會在執行commit語句的時候釋放。

由于字段d上沒有索引,是以這條查詢語句會做全表掃描,那麼,其他被掃描的不滿足的行記錄會不會被加鎖?

幻讀現象

如果旨在id=5這一行加鎖,而其他行不加鎖,在下面這個情況下:

《MySQL——幻讀與next-key lock與間隙鎖帶來的死鎖》

session A執行了三次目前讀,并且加上了寫鎖。

幻讀指的是一個事務在前後兩次查詢同一個範圍的時候,後一次查詢看到了前一次查詢沒有看到的行。

幻讀與不可重複讀的差別

在同一個事務中,兩次讀取到的資料不一緻的情況稱為幻讀和不可重複讀。幻讀是針對insert導緻的資料不一緻,不可重複讀是針對 delete、update導緻的資料不一緻。

1、在可重複讀隔離級别下,普通的查詢是快照讀,是不會看到别的事務插入的資料的。

2、session B的修改結果被session A之後的select語句用目前讀看到,不能稱為幻讀。幻讀僅僅指"新插入的行"

幻讀帶來的問題

1、破壞語義。

session A在T1就說了,把d=5的行鎖住,不準别的事務進行讀寫,此時被破壞。

因為如果我們這時插入d=5的資料,這條新的資料不在鎖的保護範圍之内。

2、資料一緻性問題

鎖的設計是為了保證資料的一緻性,不止是資料庫内部資料狀态在此刻的一緻性,還包含了資料和日志在邏輯上的一緻性。

即使給所有行加上了鎖,也避免不了幻讀,這是因為給行加鎖的時候,這條記錄還不存在,沒法加鎖 。

也就是說即使把所有的記錄都上鎖了,還是阻止不了新插入的記錄

如何解決幻讀

産生的幻讀的原因是:行鎖隻能鎖住行

為了解決幻讀問題,InnoDB引入新的鎖:間隙鎖(Gap Lock)

間隙鎖,鎖的就是兩個值之間的空隙,比如在表t,初始化插入了6個記錄,就産生了7個間隙:

《MySQL——幻讀與next-key lock與間隙鎖帶來的死鎖》

執行:

select * from t where d = 5 for update      

6個記錄加上了行鎖,同時加上了7個間隙鎖。

間隙鎖與行鎖有點不一樣

行鎖可以分為讀鎖與寫鎖

《MySQL——幻讀與next-key lock與間隙鎖帶來的死鎖》

與行鎖有沖突關系的是另外一個行鎖。

間隙鎖不一樣,間隙鎖之間不存在沖突關系。

與間隙鎖存在沖突關系的,是"向間隙中插入一個記錄"這個操作。

舉例:

《MySQL——幻讀與next-key lock與間隙鎖帶來的死鎖》

由于表t中并沒有c=7這個記錄,是以session A加的是間隙鎖(5,10)。而session B也是在這個間隙加的間隙鎖,它們的目标都是保護這個間隙,不允許插入值,是以兩者不沖突。

next-key lock

間隙鎖與行鎖合稱next-key lock,每個lock都是前開後閉區間。間隙鎖是開區間。

如上面我們插入資料,使用:

select * from t for update      

形成了7個next-key lock,分别是:

(-∞,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20, 25]、(25, +supremum]

supremum是一個不存在的最大值。

next-key lock 的引入解決了幻讀問題,但是也帶來了新的問題。

如,現在有這樣一個業務邏輯:

任意鎖住一行,如果這一行不存在的話就插入,如果存在這一行就更新它的資料。

begin;
select * from t where id = N for update;
--如果行不存在
insert into t values(N,N,N);
--如果行存在
update t set d = N set id = N;

commit;      

現在出現這個現象:這個邏輯一旦有并發,就會碰到死鎖。

《MySQL——幻讀與next-key lock與間隙鎖帶來的死鎖》

死鎖的産生:兩個間隙鎖不沖突,互相等待行鎖

執行流程:

1、session A執行select…for update語句,由于id=9這一行不存在,是以會加上間隙鎖(5,10)

2、session B執行select…for update語句,同樣會加上間隙鎖(5,10)

3、session B插入(9,9,9),被session A的間隙鎖鎖住,進入等待

4、session A擦汗如·插入(9,9,9),被session B的間隙鎖鎖住。

InnoDB死鎖檢測發現了這對死鎖關系,然後報錯傳回了。