接上篇事務(wù)隔離級(jí)別和幻讀,留了個(gè)坑,沒想到竟然過了10天,時(shí)間不注意真的過的好快。順便提下,簡(jiǎn)書圖片鏈接是屬于網(wǎng)站的,開發(fā)自己的圖床迫在眉睫,萬一哪天遷移就要做很多額外工作,一些概念或者思路用圖片表達(dá)更直觀清楚。
回到正題,之前提到一般情況下MySQL的InnoDB引擎在可重復(fù)讀的情況下是沒法保證不出現(xiàn)幻讀的,但實(shí)際情況是MySQL可以通過加鎖來防止幻讀的出現(xiàn),這種鎖定通過Next-key機(jī)制來實(shí)現(xiàn),是屬于記錄鎖和間隙鎖(Gap鎖)的結(jié)合。
引申,行級(jí)別鎖的三種算法:
- Record lock
- Gap lock
- Next-Key lock
record lock 按照索引記錄加鎖,如果沒有則采用隱式的主鍵來鎖定。
next-key 結(jié)合了Gap lock 和 record lock ,例如索引值:10,11,13,20。則按next-key 鎖定的區(qū)間為(-∞,10],(10,11]
,(11,13],(13,20],(20,+∞)
注意此處指非唯一索引,如果是唯一索引,會(huì)降級(jí)為Record lock ,僅僅鎖住索引本身,而非范圍。同時(shí)這種降級(jí)只發(fā)生在查詢所有索引列的情況下,如果存在聯(lián)合索引且只是查詢聯(lián)合索引的某一列,即屬于range類型查詢,仍是采用Next-key鎖定,不會(huì)降級(jí)。
舉個(gè)存在唯一索引和輔助索引的例子做說明:
create table test ( a int , b int , primary key (a), key(b));
insert into test select 1, 1;
insert into test select 3, 1;
insert into test select 5, 3;
insert into test select 7, 6;
insert into test select 10,8;
執(zhí)行 select * from test where b = 3 for update
存在兩個(gè)索引,分別加鎖,唯一主鍵列a加record lock , 輔助索引列b加next-key lock (1,3) 以及給下一個(gè)值的區(qū)間(3,6)加gap鎖;
因此在另一個(gè)事務(wù)里執(zhí)行以下語(yǔ)句都會(huì)阻塞,具體分析:
select * from test where a = 5 lock in share mode;
insert into test select 4,2;
insert into test select 6,5;
第一個(gè)阻塞因?yàn)榧恿宋ㄒ凰饕膔ecord lock a = 5;
第二個(gè)主鍵插入4,符合條件,但是根據(jù)輔助索引b 的范圍, b = 2 在(1,3)中,同樣阻塞;
第三個(gè)a =6 不在主鍵a鎖定范圍,b = 5 也不在輔助索引b 的范圍(1,3)中,但在另一個(gè)gap鎖范圍(3,6)中,因此也阻塞;
這種鎖定情形下,可以執(zhí)行的包括類似語(yǔ)句:
insert into test select 8,6;
insert into test select 2,0;
gap lock 可以通過設(shè)置隔離級(jí)別為讀已提交或者將innodb_locks_unsafe_for_binlog = 1取消,但是從隔離性,性能,以及主從數(shù)據(jù)一致等方面都不建議這樣做。
insert的特殊情況
對(duì)于insert 會(huì)檢查下一條記錄是否被鎖定,如上述例子有select * from test where b = 3 for update
插入insert into test select 2,2
會(huì)檢測(cè)到b = 3 已經(jīng)被鎖定,而insert into test select 2,0
可以執(zhí)行;
[1]:《MySQL技術(shù)內(nèi)幕:InnoDB存儲(chǔ)引擎》-第六章:鎖