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這一行加鎖,而其他行不加鎖,在下面這個情況下:

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個間隙:
執行:
select * from t where d = 5 for update
6個記錄加上了行鎖,同時加上了7個間隙鎖。
間隙鎖與行鎖有點不一樣
行鎖可以分為讀鎖與寫鎖
與行鎖有沖突關系的是另外一個行鎖。
間隙鎖不一樣,間隙鎖之間不存在沖突關系。
與間隙鎖存在沖突關系的,是"向間隙中插入一個記錄"這個操作。
舉例:
由于表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;
現在出現這個現象:這個邏輯一旦有并發,就會碰到死鎖。
死鎖的産生:兩個間隙鎖不沖突,互相等待行鎖
執行流程:
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死鎖檢測發現了這對死鎖關系,然後報錯傳回了。