MySQL數據庫InnoDB引擎行級鎖鎖定範圍詳解

前言

每一個數據庫幾乎都會實現本身的鎖機制,鎖機制是數據庫區別於文件系統的主要標誌之一,用於管理對共享資源的併發訪問。 html

Mysql數據庫InnoDB引擎支持行級鎖,也就是說咱們能夠對錶中某些行數據執行鎖定操做,鎖定操做的影響是:若是一個事物對錶中某行執行了鎖定操做,而另外一個事務也須要對一樣的行執行鎖定操做,這樣第二個事務的鎖定操做有可能被阻塞,一旦被阻塞第二個事務只能等到第一個事務執行完畢(提交或回滾)或超時。mysql

本文主要介紹InnoDB中的行鎖相關概念,重點介紹行鎖的鎖定範圍:sql

  • 什麼樣的SQL語句會加鎖?
  • 加什麼樣的鎖?
  • 加鎖語句會鎖定哪些行

背景知識

上面咱們簡單的介紹了InnoDB的行級鎖,爲了理解後面的驗證部分,須要補充一下背景知識。若是對相應知識很是瞭解,能夠直接跳轉到驗證部份內容。數據庫

1. InnoDB鎖的類型

InnoDB引擎使用了七種類型的鎖,他們分別是:編程

  • 共享排他鎖(Shared and Exclusive Locks)
  • 意向鎖(Intention Locks)
  • 記錄鎖(Record Locks)
  • 間隙鎖(Gap Locks)
  • Next-Key Locks
  • 插入意圖鎖(Insert Intention Locks)
  • 自增鎖(AUTO-INC Locks)

本文主要涉及Shared and Exclusive Locks,Record Locks,Gap Locks,Next-Key Locks這幾種鎖,其餘類型鎖若是你們感興趣能夠本身深刻了解,在此不在詳述。併發

1.1 Shared and Exclusive Locks

共享鎖(S鎖)和排他鎖(X鎖)的概念在許多編程語言中都出現過。先來描述一下這兩種鎖在MySQL中的影響結果:編程語言

  • 若是一個事務對某一行數據加了S鎖,另外一個事務還能夠對相應的行加S鎖,可是不能對相應的行加X鎖。
  • 若是一個事務對某一行數據加了X鎖,另外一個事務既不能對相應的行加S鎖也不能加X鎖。

用一張經典的矩陣表格繼續說明共享鎖和排他鎖的互斥關係:測試

-- S X
S 0 1
X 1 1

圖中S表示共享鎖X表示獨佔鎖,0表示鎖兼容1表示鎖衝突,兼容不被阻塞,衝突被阻塞。由表可知一旦一個事務加了排他鎖,其餘個事務加任何鎖都須要等待。多個共享鎖不會相互阻塞。spa

1.2 Record Locks、Gap Locks、Next-Key Locks

這三種類型的鎖都描述了鎖定的範圍,故放在一塊兒說明。code

如下定義摘自MySQL官方文檔

  • 記錄鎖(Record Locks):記錄鎖鎖定索引中一條記錄。
  • 間隙鎖(Gap Locks):間隙鎖要麼鎖住索引記錄中間的值,要麼鎖住第一個索引記錄前面的值或者最後一個索引記錄後面的值。
  • Next-Key Locks:Next-Key鎖是索引記錄上的記錄鎖和在索引記錄以前的間隙鎖的組合。

定義中都提到了索引記錄(index record)。爲何?行鎖和索引有什麼關係呢?其實,InnoDB是經過搜索或者掃描表中索引來完成加鎖操做,InnoDB會爲他遇到的每個索引數據加上共享鎖或排他鎖。因此咱們能夠稱行級鎖(row-level locks)爲索引記錄鎖(index-record locks),由於行級鎖是添加到行對應的索引上的。

三種類型鎖的鎖定範圍不一樣,且逐漸擴大。咱們來舉一個例子來簡要說明各類鎖的鎖定範圍,假設表t中索引列有三、五、八、9四個數字值,根據官方文檔的肯定三種鎖的鎖定範圍以下:

  • 記錄鎖的鎖定範圍是單獨的索引記錄,就是三、五、八、9這四行數據。
  • 間隙鎖的鎖定爲行中間隙,用集合表示爲(-∞,3)、(3,5)、(5,8)、(8,9)、(9,+∞)。
  • Next-Key鎖是有索引記錄鎖加上索引記錄鎖以前的間隙鎖組合而成,用集合的方式表示爲(-∞,3]、(3,5]、(5,8]、(8,9]、(9,+∞)。

最後對於間隙鎖還須要補充三點:

  1. 間隙鎖阻止其餘事務對間隙數據的併發插入,這樣可有有效的解決幻讀問題(Phantom Problem)。正由於如此,並非全部事務隔離級別都使用間隙鎖,MySQL InnoDB引擎只有在Repeatable Read(默認)隔離級別才使用間隙鎖。
  2. 間隙鎖的做用只是用來阻止其餘事務在間隙中插入數據,他不會阻止其餘事務擁有一樣的的間隙鎖。這就意味着,除了insert語句,容許其餘SQL語句能夠對一樣的行加間隙鎖而不會被阻塞
  3. 對於惟一索引的加鎖行爲,間隙鎖就會失效,此時只有記錄鎖起做用

2. 加鎖語句

前面咱們已經介紹了InnoDB的是在SQL語句的執行過程當中經過掃描索引記錄的方式來實現加鎖行爲的。那哪些些語句會加鎖?加什麼樣的鎖?接下來咱們逐一描述:

  • select ... from語句:InnoDB引擎採用多版本併發控制(MVCC)的方式實現了非阻塞讀,因此對於普通的select讀語句,InnoDB並不會加鎖【注1】。
  • select ... from lock in share mode語句:這條語句和普通select語句的區別就是後面加了lock in share mode,經過字面意思咱們能夠猜到這是一條加鎖的讀語句,而且鎖類型爲共享鎖(讀鎖)。InnoDB會對搜索的全部索引記錄加next-key鎖,可是若是掃描的惟一索引的惟一行,next-key降級爲索引記錄鎖。
  • select ... from for update語句:和上面的語句同樣,這條語句加的是排他鎖(寫鎖)。InnoDB會對搜索的全部索引記錄加next-key鎖,可是若是掃描惟一索引的惟一行,next-key降級爲索引記錄鎖。
  • update ... where ...語句:。InnoDB會對搜索的全部索引記錄加next-key鎖,可是若是掃描惟一索引的惟一行,next-key降級爲索引記錄鎖。【注2】
  • delete ... where ...語句:。InnoDB會對搜索的全部索引記錄加next-key鎖,可是若是掃描惟一索引的惟一行,next-key降級爲索引記錄鎖。
  • insert語句:InnoDB只會在將要插入的那一行上設置一個排他的索引記錄鎖。

最後補充兩點:

  1. 若是一個查詢使用了輔助索引而且在索引記錄加上了排他鎖,InnoDB會在相對應的聚合索引記錄上加鎖。
  2. 若是你的SQL語句沒法使用索引,這樣MySQL必須掃描整個表以處理該語句,致使的結果就是表的每一行都會被鎖定,而且阻止其餘用戶對該表的全部插入。

SQL語句驗證

閒言少敘,接下來咱們進入本文重點SQL語句驗證部分。

1.測試環境

數據庫:MySQL 5.6.35
事務隔離級別:Repeatable read
數據庫訪問終端:mysql client

2.驗證場景

2.1 場景一

建表:

CREATE TABLE `user` (
 `id` int(11) NOT NULL,
 `name` varchar(8) NOT NULL,
 PRIMARY KEY (`id`),
 KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

插入數據:

INSERT INTO `user` (`id`, `name`) VALUES ('1', 'a');
INSERT INTO `user` (`id`, `name`) VALUES ('3', 'c');
INSERT INTO `user` (`id`, `name`) VALUES ('5', 'e');
INSERT INTO `user` (`id`, `name`) VALUES ('7', 'g');
INSERT INTO `user` (`id`, `name`) VALUES ('9', 'i');

首先咱們執行SQL語句的模板:

步驟 client 1 client 2
1 begin; --
2 SELECT * FROM user where name='e' for update; --
3 -- begin;
4 -- INSERT INTO `user` (`id`, `name`) VALUES (10, #{name});
5 rollback; --
6 -- rollback;

替換步驟5中name的值,觀察結果:

name的值 執行結果
a 不阻塞
b 不阻塞
d 阻塞
e 阻塞
f 阻塞
h 不阻塞
i 不阻塞

觀察結果,咱們發現SQL語句
SELECT * FROM user where name='e' for update
一共鎖住索引name中三行記錄,(c,e]區間應該是next-key鎖而(e,h)區間是索引記錄e後面的間隙。

接下來咱們肯定next-key鎖中哪部分是索引記錄鎖哪部分是間隙鎖。

執行SQL語句的模板:

步驟 client 1 client 2
1 begin; --
2 SELECT * FROM user where name='e' for update; --
3 -- SELECT * FROM user where name=#{name} for update;
5 rollback; --
6 -- rollback;

替換步驟5中name的值,觀察結果:

name的值 執行結果
d 不阻塞
e 阻塞
f 不阻塞

由於間隙鎖只會阻止insert語句,因此一樣的索引數據,insert語句阻塞而select for update語句不阻塞的就是間隙鎖,若是兩條語句都阻塞就是索引記錄鎖。

觀察執行結果可知,d和f爲間隙鎖,e爲索引記錄鎖。

結論:經過兩條SQL,咱們肯定了對於輔助索引name在查詢條件爲 where name='e' 時的加鎖範圍爲(c,e],(e,g),其中:

  • 對SQL語句掃描的索引記錄e加索引記錄鎖[e]。
  • 鎖定了e前面的間隙,c到e之間的數據(c,e)加了間隙鎖
  • 前兩個構成了next-key鎖(c,e]。
  • 值得注意的是還鎖定了e後面的間隙(e,g)。

說的這裏細心的讀者可能已經發現咱們的測試數據中沒有間隙的邊界數據c和g。接下來咱們就對間隙邊界值進行測試。

執行SQL語句的模板:

步驟 client 1 client 2
1 begin; --
2 SELECT * FROM user where name='e' for update; --
3 -- begin;
4 -- INSERT INTO `user` (`id`, `name`) VALUES (#{id}, #{name});
5 rollback; --
6 -- rollback;

替換步驟5中id,name的值,觀察結果:

id的值 name=c 執行結果 id的值 name=g 執行結果
-- -- -- -3 g 組塞
-- -- -- -2 g 阻塞
-1 c 不阻塞 -1 g 阻塞
1 c 不阻塞 1 g 不阻塞
2 c 不阻塞 2 g 阻塞
3 c 不阻塞 3 g 不阻塞
4 c 阻塞 4 g 阻塞
5 c 阻塞 5 g 阻塞
6 c 阻塞 6 g 阻塞
7 c 不阻塞 7 g 不阻塞
8 c 阻塞 8 g 不阻塞
9 c 不阻塞 9 g 不阻塞
10 c 阻塞 10 g 不阻塞
11 c 阻塞 - - -
12 c 阻塞 - - -

經過觀察以上執行結果,咱們發現,name等於c和e時insert語句的結果隨着id值得不一樣一下子鎖定,一下子不鎖定。那必定是id列加了鎖纔會形成這樣的結果。

若是先不看id=5這一行數據的結果,咱們發現一個規律:

  • name=c時,name=c對應的id=3的id聚合索引數據記錄以後的間隙(3,5),(5,7),(7,9),(9,∞)都被加上了鎖。
  • name=e時,name=e對應的id=7的id聚合索引數據記錄以前的間隙(5,7),(3,5),(1,3),(-∞,1)都被加上了鎖。
  • 咱們可用select * from user where id = x for update;語句判斷出以上間隙上加的鎖都爲間隙鎖。

接下來咱們解釋一下id=5的鎖定狀況

執行SQL語句的模板:

步驟 client 1 client 2
1 begin; --
2 SELECT * FROM user where name='e' for update; --
3 -- SELECT * FROM user where id=#{id} for update;
5 rollback; --
6 -- rollback;

替換步驟5中id的值,觀察結果:

id的值 執行結果
3 不阻塞
4 不阻塞
5 阻塞
6 不阻塞
7 不阻塞

經過觀察執行結果可知,id=5的聚合索引記錄上添加了索引記錄鎖。根據MySQL官方文檔描述,InnoDB引擎在對輔助索引加鎖的時候,也會對輔助索引所在行所對應的聚合索引(主鍵)加鎖。而主鍵是惟一索引,在對惟一索引加鎖時,間隙鎖失效,只使用索引記錄鎖。因此SELECT * FROM user where name='e' for update;不只對輔助索引name=e列加上了next-key鎖,還對對應的聚合索引id=5列加上了索引記錄鎖。

最終結論:
對於SELECT * FROM user where name='e' for update;一共有三種鎖定行爲:

  1. 對SQL語句掃描過的輔助索引記錄行加上next-key鎖(注意也鎖住記錄行以後的間隙)。
  2. 對輔助索引對應的聚合索引加上索引記錄鎖。
  3. 當輔助索引爲間隙鎖「最小」和「最大」值時,對聚合索引相應的行加間隙鎖。「最小」鎖定對應聚合索引以後的行間隙。「最大」值鎖定對應聚合索引以前的行間隙。

上面咱們將對輔助索引加鎖的狀況介紹完了,接下來咱們測試一下對聚合索引和惟一索引加鎖。

2.2 場景二

建表:

CREATE TABLE `user` (
  `id` int(11) NOT NULL,
  `name` varchar(8) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `index_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

注意與場景一表user不一樣的是name列爲惟一索引。

插入數據:

INSERT INTO `user` (`id`, `name`) VALUES ('1', 'a');
INSERT INTO `user` (`id`, `name`) VALUES ('3', 'c');
INSERT INTO `user` (`id`, `name`) VALUES ('5', 'e');
INSERT INTO `user` (`id`, `name`) VALUES ('7', 'g');
INSERT INTO `user` (`id`, `name`) VALUES ('9', 'i');

首先咱們執行SQL語句的模板:

步驟 client 1 client 2
1 begin; --
2 SELECT * FROM user where name='e' for update;
3 -- begin;
4 -- INSERT INTO `user` (`id`, `name`) VALUES (10, #{name});
5 rollback; --
6 -- rollback;

替換步驟5中name的值,觀察結果:

name的值 執行結果
a 不阻塞
b 不阻塞
c 不阻塞
d 不阻塞
e 阻塞
f 不阻塞
g 不阻塞
h 不阻塞
i 不阻塞

由測試結果可知,只有name='e'這行數據被鎖定。

經過SQL語句咱們驗證了,對於惟一索引列加鎖,間隙鎖失效,

2.3 場景三

場景一和場景二都是在查詢條件等於的狀況下作出的範圍判斷,如今咱們嘗試一下其餘查詢條件,看看結論是否一致。

借用場景一的表和數據。

建表:

CREATE TABLE `user` (
  `id` int(11) NOT NULL,
  `name` varchar(8) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `index_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

插入數據:

INSERT INTO `user` (`id`, `name`) VALUES ('1', 'a');
INSERT INTO `user` (`id`, `name`) VALUES ('3', 'c');
INSERT INTO `user` (`id`, `name`) VALUES ('5', 'e');
INSERT INTO `user` (`id`, `name`) VALUES ('7', 'g');
INSERT INTO `user` (`id`, `name`) VALUES ('9', 'i');

執行SQL語句的模板:

步驟 client 1 client 2
1 begin; --
2 SELECT * FROM user where name>'e' for update; --
3 -- begin;
4 -- INSERT INTO `user` (`id`, `name`) VALUES ('10', #{name});
5 rollback; --
6 -- rollback;

替換步驟5中name的值,觀察結果:

name的值 執行結果
a 阻塞
b 阻塞
c 阻塞
d 阻塞
e 阻塞
f 阻塞
g 阻塞
h 阻塞
i 阻塞

這個結果是否是和你想象的不太同樣,這個結果代表where name>'e'這個查詢條件並非鎖住'e'列以後的數據,而鎖住了全部name列中全部數據和間隙。這是爲何呢?

咱們執行如下的SQL語句執行計劃:

explain select * from user where name>'e' for update;

執行結果:

+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+
| id | select_type | table | type  | possible_keys | key        | key_len | ref  | rows | Extra                    |
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+
|  1 | SIMPLE      | user  | index | index_name    | index_name | 26      | NULL |    5 | Using where; Using index |
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+
1 row in set (0.00 sec)

若是你的結果與上面不一樣先執行一下OPTIMIZE TABLE user;再執行以上語句。

經過觀察SQL語句的執行計劃咱們發現,語句使用了name列索引,且rows參數等於5,user表中一共也只有5行數據。SQL語句的執行過程當中一共掃描了name索引記錄5行數據且對這5行數據都加上了next-key鎖,符合咱們上面的執行結果。

接下來咱們再製造一組數據。
建表:

CREATE TABLE `user` (
 `id` int(11) NOT NULL,
 `name` varchar(8) NOT NULL,
 `age` int(11) NOT NULL,
 PRIMARY KEY (`id`),
 KEY `index_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

插入數據:

INSERT INTO `user` (`id`, `name`,`age`) VALUES ('1', 'a','15');
INSERT INTO `user` (`id`, `name`,`age`) VALUES ('3', 'c','20');
INSERT INTO `user` (`id`, `name`,`age`) VALUES ('5', 'e','16');
INSERT INTO `user` (`id`, `name`,`age`) VALUES ('7', 'g','19');
INSERT INTO `user` (`id`, `name`,`age`) VALUES ('9', 'i','34');

這張表和前表的區別是多了一列非索引列age

咱們再執行一下一樣的SQL語句執行計劃:

explain select * from user where name>'e' for update;

執行結果:

+----+-------------+-------+-------+---------------+------------+---------+------+------+-----------------------+
| id | select_type | table | type  | possible_keys | key        | key_len | ref  | rows | Extra                 |
+----+-------------+-------+-------+---------------+------------+---------+------+------+-----------------------+
|  1 | SIMPLE      | user  | range | index_name    | index_name | 26      | NULL |    2 | Using index condition |
+----+-------------+-------+-------+---------------+------------+---------+------+------+-----------------------+
1 row in set (0.00 sec)

是否是和第一次執行結果不一樣了,rows參數等於2,說明掃描了兩行記錄,結合SQL語句select * from user where name>'e' for update;執行後返回結果咱們判斷這兩行記錄應該爲g和i。

由於select * from user where name>'e' for update;語句掃描了兩行索引記錄分別是g和i,因此咱們將g和i的鎖定範圍疊就能夠獲得where name>'e'的鎖定範圍:

  1. 索引記錄g在name列鎖定範圍爲(e,g],(g,i)。索引記錄i的在name列鎖定範圍爲(g,i],(i,+∞)。二者疊加後鎖定範圍爲(e,g],(g,i],(i,+∞)。其中g,i爲索引記錄鎖。
  2. g和i對應id列中的7和9加索引記錄鎖。
  3. name列的值爲鎖定範圍上邊界e時,還會在e所對應的id列值爲5以後的全部值之間加上間隙鎖,範圍爲(5,7),(7,9),(9,+∞)。下邊界爲+∞無需考慮。

接下來咱們逐一測試:

首先測試驗證了next-key鎖範圍,執行SQL語句的模板:

步驟 client 1 client 2
1 begin; --
2 SELECT * FROM user where name>'e' for update; --
3 -- begin;
4 -- INSERT INTO `user` (`id`, `name`, `age`) VALUES ('10', #{name},'18');
5 rollback; --
6 -- rollback;

替換步驟5中name的值,觀察結果:

name的值 執行結果
a 不阻塞
b 不阻塞
c 不阻塞
d 不阻塞
f 阻塞
g 阻塞
h 阻塞
i 阻塞
j 阻塞
k 阻塞

下面驗證next-key鎖中哪部分是間隙鎖,哪部分是索引記錄鎖,執行SQL語句的模板:

步驟 client 1 client 2
1 begin; --
2 SELECT * FROM user where name>'e' for update; --
3 -- SELECT * FROM user where name=#{name} for update;
5 rollback; --
6 -- rollback;

替換步驟5中name的值,觀察結果:

name的值 執行結果
e 不阻塞
f 不阻塞
g 阻塞
h 不阻塞
i 阻塞
j 不阻塞

接下來驗證對id列加索引記錄鎖,執行SQL語句的模板:

步驟 client 1 client 2
1 begin; --
2 SELECT * FROM user where name>'e' for update; --
3 -- SELECT * FROM user where id=#{id} for update;
5 rollback; --
6 -- rollback;

替換步驟5中id的值,觀察結果:

id的值 執行結果
5 不阻塞
6 不阻塞
7 阻塞
8 不阻塞
9 阻塞
10 不阻塞

最後咱們驗證name列的值爲邊界數據e時,id列間隙鎖的範圍,執行SQL語句的模板:

步驟 client 1 client 2
1 begin; --
2 SELECT * FROM user where name>'e' for update; --
3 -- begin;
4 -- INSERT INTO `user` (`id`, `name`,`age`) VALUES (#{id}, 'e','18');
5 rollback; --
6 -- rollback;

替換步驟5中id的值,觀察結果:

id的值 執行結果
-1 不阻塞
1 不阻塞
2 不阻塞
3 不阻塞
4 不阻塞
5 不阻塞
6 阻塞
7 阻塞
8 阻塞
9 阻塞
10 阻塞
11 阻塞
12 阻塞

注意7和9是索引記錄鎖記錄鎖

觀察上面的全部SQL語句執行結果,能夠驗證select * from user where name>'e' for update的鎖定範圍爲此語句掃描name列索引記錄g和i的鎖定範圍的疊加組合。

2.4 場景四

咱們經過場景三驗證了普通索引的範圍查詢語句加鎖範圍,如今咱們來驗證一下惟一索引的範圍查詢狀況下的加鎖範圍。有了場景三的鋪墊咱們直接跳過掃描所有索引的狀況,建立能夠掃描範圍記錄的表結構並插入相應數據測試。

建表:

CREATE TABLE `user` (
  `id` int(11) NOT NULL,
  `name` varchar(8) NOT NULL,
  `age` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `index_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

插入數據:

INSERT INTO `user` (`id`, `name`,`age`) VALUES ('1', 'a','15');
INSERT INTO `user` (`id`, `name`,`age`) VALUES ('3', 'c','20');
INSERT INTO `user` (`id`, `name`,`age`) VALUES ('5', 'e','16');
INSERT INTO `user` (`id`, `name`,`age`) VALUES ('7', 'g','19');
INSERT INTO `user` (`id`, `name`,`age`) VALUES ('9', 'i','34');

和場景三表惟一不一樣是name列爲惟一索引。

SQL語句select * from user where name>'e'掃描name列兩條索引記錄g和i。若是須要只對g和i這兩條記錄加上記錄鎖沒法避免幻讀的發生,索引鎖定範圍應該仍是兩條數據next-key鎖鎖的組合:(e,g],(g,i],(i,+∞)。其中g,i爲索引記錄鎖

咱們經過SQL驗證咱們的結論,執行SQL語句的模板:

步驟 client 1 client 2
1 begin; --
2 SELECT * FROM user where name>'e' for update; --
3 -- begin;
4 -- INSERT INTO `user` (`id`, `name`, `age`) VALUES ('10', #{name},'18');
5 rollback; --
6 -- rollback;

替換步驟5中name的值,觀察結果:

name的值 執行結果
a 不阻塞
b 不阻塞
c 不阻塞
d 不阻塞
f 阻塞
g 阻塞
h 阻塞
i 阻塞
j 阻塞
k 阻塞

下面驗證next-key鎖中哪部分是間隙鎖,哪部分是索引記錄鎖,執行SQL語句的模板:

步驟 client 1 client 2
1 begin; --
2 SELECT * FROM user where name>'e' for update; --
3 -- SELECT * FROM user where name=#{name} for update;
5 rollback; --
6 -- rollback;

替換步驟5中name的值,觀察結果:

name的值 執行結果
e 不阻塞
f 不阻塞
g 阻塞
h 不阻塞
i 阻塞
j 不阻塞

經過上面兩條SQL語句的驗證結果,咱們證實了咱們的g和i的鎖定範圍趨勢爲二者next-key疊加組合。

接下來咱們驗證一下對輔助索引加鎖後對聚合索引的鎖轉移,執行SQL語句的模板:

步驟 client 1 client 2
1 begin; --
2 SELECT * FROM user where name>'e' for update; --
3 -- SELECT * FROM user where id=#{id} for update;
5 rollback; --
6 -- rollback;

替換步驟5中id的值,觀察結果:

id的值 執行結果
5 不阻塞
6 不阻塞
7 阻塞
8 不阻塞
9 阻塞
10 不阻塞

由結果可知對輔助索引name中的g和i列對應的聚合索引id列中的7和9加上了索引記錄鎖。

到目前爲止全部實驗結果和場景三徹底同樣,這也很好理解,畢竟場景四和場景三隻是輔助索引name的索引類型不一樣,一個是惟一索引,一個是普通索引。

最後驗證意向,next-key鎖邊界數據e,看看結論時候和場景三相同。

執行SQL語句的模板:

步驟 client 1 client 2
1 begin; --
2 SELECT * FROM user where name>'e' for update; --
3 -- begin;
4 -- INSERT INTO `user` (`id`, `name`,`age`) VALUES (#{id}, 'e','18');
5 rollback; --
6 -- rollback;

替換步驟5中id的值,觀察結果:

id的值 執行結果
-1 不阻塞
1 不阻塞
2 不阻塞
3 不阻塞
4 不阻塞
5 不阻塞
6 不阻塞
7 阻塞
8 不阻塞
9 阻塞
10 不阻塞
11 不阻塞
12 不阻塞

注意7和9是索引記錄鎖記錄鎖

經過結果可知,當name列爲索引記錄上邊界e時,並無對id有加鎖行爲,這點與場景三不一樣。

對於惟一索引的範圍查詢和普通索引的範圍查詢相似,惟一不一樣的是當輔助索引等於上下範圍的邊界值是不會對主鍵加上間隙鎖。

惟一索引範圍查詢加鎖範圍:

  • 對於掃描的輔助索引記錄的鎖定範圍就是多個索引記錄next-key範圍的疊加組合。
  • 對於聚合索引(主鍵)的鎖定範圍,會對多個輔助索引對應的聚合索引列加索引記錄鎖。

結論

InnoDB引擎會對他掃描過的索引記錄加上相應的鎖,經過「場景一」咱們已經明確了掃描一條普通索引記錄的鎖定範圍,經過「場景三」咱們能夠推斷任意多個掃描普通索引索引記錄的鎖定範圍。經過「場景二」咱們肯定了掃描一條惟一索引記錄(或主鍵)的鎖定範圍。經過「場景四」咱們能夠推斷任意多個掃描索惟一引記錄(或主鍵)的鎖定範圍。在實際的應用能夠靈活使用,判斷兩條SQL語句是否相互鎖定。這裏還須要注意的是對於索引的查詢條件,不能想固然的理解,他每每不是咱們理解的樣子,須要結合執行計劃判斷索引最終掃描的記錄數,不然會對加鎖範圍理解產生誤差。




備註

注1:在事務隔離級別爲SERIALIZABLE時,普通的select語句也會對語句執行過程當中掃描過的索引加上next-key鎖。若是語句掃描的是惟一索引,那就將next-key鎖降級爲索引記錄鎖了。
注2:當更新語句修改聚合索引(主鍵)記錄時,會對受影響的輔助索引執行隱性的加鎖操做。當插入新的輔助索引記錄以前執行重複檢查掃描時和當插入新的輔助索引記錄時,更新操做還對受影響的輔助索引記錄添加共享鎖。



參考:

https://dev.mysql.com/doc/ref...
https://dev.mysql.com/doc/ref...

相關文章
相關標籤/搜索