中心思想
間隙鎖鎖的是索引葉子節點的next指針。
意義
解決了mysql RR級別下是幻讀的問題。
快照讀
在RR隔離級別下:快照讀有可能讀到數據的歷史版本,也有可能讀到數據的當前版本。所以快照讀無需用鎖也不會發生幻讀的情況。
當前讀
當前讀:select…lock in share mode,select…for update
當前讀:update,delete,insert
讀取的是記錄的最新版本,所以所以就需要通過加鎖(行鎖 間隙鎖 表鎖)的方式,使得被當前讀讀過的數據不能被新增修改或者刪除,換句話說再來一次當前讀要返回相同的數據。
為什么需要間隙鎖
數據表
CREATE TABLE `z` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`b` int(11) DEFAULT NULL,
`c` int(255) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `b` (`b`)
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8;
INSERT INTO `study`.`z` (`id`, `b`, `c`) VALUES ('1', '1', '0');
INSERT INTO `study`.`z` (`id`, `b`, `c`) VALUES ('3', '6', '1');
INSERT INTO `study`.`z` (`id`, `b`, `c`) VALUES ('5', '4', '2');
INSERT INTO `study`.`z` (`id`, `b`, `c`) VALUES ('7', '8', '3');
INSERT INTO `study`.`z` (`id`, `b`, `c`) VALUES ('8', '10', '4');
索引B結構
鎖加在哪里
begin; select * from z where b = 6 for update;
這條sql語句之后看看我們 需要做什么才能保證不發生幻讀。
1不能插入b為6的數據
2不能刪除b為6的數據
3不能修改b為6的數據
4不能把別的數據修改為b為6
突然一看挺復雜的,這個鎖要怎么加呢,mysql大牛靈機一動,給葉子節點5的next指針加鎖,給葉子節點3加行鎖,給葉子節點3的next指針加鎖。如下圖所示
這樣不就能把上述四個問題解決了么,兩個next指針鎖解決了插入b為6或者把別的數據修改為b為6,行鎖解決了修改b為6的行,但是呢也帶來一些明顯的副作用。
例如
INSERT INTO `study`.`z` (`id`, `b`, `c`) VALUES ('6', '4', '0');
會bolck因為按照索引結構這條數據會插入到葉子結點5和3之間,會修改葉子節點5的next指針,雖然這條sql沒有破壞上述的4個紅色條件但是依然被阻塞了所以我叫它為副作用。
INSERT INTO `study`.`z` (`id`, `b`, `c`) VALUES ('4', '4', '0');
插入成功因為這條數據會插入在1的后面5的前面。
現在大家是不是能理解間隙鎖的怪異行為了呢。
間隙鎖范圍
begin;
select * from z where id=4 for update;
會鎖住主鍵索引葉子節點的3的next指針。(為啥呢,需要你自己畫主鍵索引的圖)
begin;
select * from z where id=3 for update;
間隙鎖會退化為行鎖只鎖葉子節點3 ,為什么因為沒必要。不加間隙鎖也不會打破上述的紅色4個條件。
begin;
select * from z where id>4 for update;
葉子節點3及之后所有節點會加行鎖並且他們的next指針會加鎖,
begin;
select * from z where c=2 for update;
會發生鎖表,因為c沒有索引結構能存儲行鎖或者間隙鎖。