首先需要明確的就是“幻讀”概念:隔離級別是可重復讀,在一個事務中前后兩次查詢蒋腮,查到了其他事務insert進來的數(shù)據(jù)任连。
強調(diào)的是讀取到了其他事務插入進來的數(shù)據(jù)。
下面來論證一下可重復讀下幻讀的解決方案
# 建表語句
CREATE TABLE `test` (
`id` int(11) NOT NULL COMMENT '主鍵',
`d` int(11) NOT NULL,
`c` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '測試表';
# 插入數(shù)據(jù)
INSERT INTO `test`(`id`, `d`, `c`) VALUES (0, 0, 0);
INSERT INTO `test`(`id`, `d`, `c`) VALUES (2, 2, 2);
INSERT INTO `test`(`id`, `d`, `c`) VALUES (3, 3, 3);
INSERT INTO `test`(`id`, `d`, `c`) VALUES (4, 4, 4);
INSERT INTO `test`(`id`, `d`, `c`) VALUES (5, 5, 5);
INSERT INTO `test`(`id`, `d`, `c`) VALUES (6, 6, 6);
INSERT INTO `test`(`id`, `d`, `c`) VALUES (7, 7, 7);
INSERT INTO `test`(`id`, `d`, `c`) VALUES (8, 8, 8);
INSERT INTO `test`(`id`, `d`, `c`) VALUES (9, 9, 9);
T1 |
T2 |
T3 |
begin; select * from TABLE where d = 5 for update; |
|
|
update TABLE set d = 5 where id = 0; |
|
select * from TABLE where d = 5 for update; |
|
|
|
|
insert into TABLE values(1,5,1); |
select * from TABLE where d = 5 for update; commit; |
|
|
先明確一下球榆,for update語法就是當前讀,也就是查詢當前已經(jīng)提交的數(shù)據(jù)禁筏,并且是帶悲觀鎖的持钉。沒有for update就是快照讀,也就是根據(jù)readView讀取的undolog中的數(shù)據(jù)篱昔。
- 當T1開啟事務每强,執(zhí)行第一條select語句時,查出來的結果如下:
- 假如該查詢只鎖定一行州刽,也就是id=5的這一行數(shù)據(jù)空执,那么在T2階段id=0并不會被鎖定,那么update執(zhí)行成功后穗椅,id=0那行數(shù)據(jù)被設置為d=5辨绊。此時T1階段第二個select查詢結果如下:
- T3階段執(zhí)行插入語句后,T1階段第三個查詢得出的結果如下:
如果按照以上猜想匹表,那么整個執(zhí)行結果就違背了可重復讀的隔離級別了门坷。
那么我們再假設select * from TABLE where d = 5 for update;這條語句鎖定的是所有被掃描到的數(shù)據(jù)宣鄙。
- 按照上述執(zhí)行邏輯,我們在T1階段第一個select讀取到的數(shù)據(jù):
- T1階段第二個select讀取到的數(shù)據(jù):
這是因為T2階段的update會被阻塞住默蚌,畢竟所有被掃描到的記錄都被鎖定了冻晤。
- 但是在T3階段依然會執(zhí)行,T3階段做的是insert操作绸吸,本身這條記錄在表中都不存在的鼻弧,也就不會被阻塞。那么T1階段第三個select查詢結果如下:
按照上述推理過程惯裕,很顯然温数,即使鎖定所有掃描到的數(shù)據(jù)行,也依然存在幻讀的情況蜻势。違背了可重復讀的隔離級別撑刺。
針對這個情況,我們要解決幻讀的問題握玛,那么就要求針對所有被掃描的記錄行以及還不存在的d=5的記錄行都給鎖住够傍。
- 在T1階段當執(zhí)行第一條select語句時,所有被掃描的記錄行都鎖住挠铲,包括d=5的不存在的記錄行冕屯。那么T2執(zhí)行的時候,就會被阻塞住拂苹,等待T1結束安聘。
- 當執(zhí)行到T1階段的第二個select時,因為T2還在等待T1結束瓢棒,所以查詢結果一樣
- 當執(zhí)行T3階段的insert語句時浴韭,因為所有d=5的不存在的記錄行也被鎖住了,也就是間隙被鎖住了脯宿,那么T3的insert語句也被阻塞念颈,等待T1結束。那么T1階段最后一條select語句執(zhí)行結果如下:
至此连霉,當前查詢結果完全滿足可重復讀的隔離級別榴芳。
通過以上推論,我們可以總結一下跺撼,在可重復讀的隔離級別下窟感,解決幻讀除了需要鎖定所有掃描到的記錄行外,還需要鎖定行之間的間隙歉井,也就是通過間隙鎖來解決幻讀的問題肌括。