select...for update,表鎖?行鎖?間隙鎖?

大家好呀,我是樓仔。

對於這個問題,我 4 年前就專門研究過,最近看到網上很多相關的文章,要麼總結得不全,要麼存在很多問題。

感覺有必要自己寫一篇,一方面對網上的知識進行糾偏,另一方面也想全面總結一下這塊知識,方便大家學習。

這篇文章應該是全網總結最全的,如果有發現比我這篇寫得更好,更全,一定要私我哈。

不 BB,上文章目錄:

01 環境準備

在驗證之前,我們先準備好具體的環境和數據,事務隔離級別 RR,數據庫版本 5.7.26

爲了方便測試,索引都是整型:

CREATE TABLE user (
  id int(11) unsigned NOT NULL AUTO_INCREMENT,
  user_no int(11) NOT NULL COMMENT '用戶編號',
  user_name varchar(16) DEFAULT NULL COMMENT '用戶名',
  age int(3) DEFAULT NULL COMMENT '年齡',
  PRIMARY KEY (id),
  UNIQUE KEY un_idx_user_no (user_no),
  KEY idx_age (age)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
複製代碼

初始化數據:

insert into user values(1, 10, '樓仔', 18);
insert into user values(4, 15, '二哥', 28);
insert into user values(8, 20, '一灰', 38);
複製代碼

常用命令操作:

> start transaction; // 開啓事務
> commit; // 提交事務
> rollback; // 回滾事務
> select @@transaction_isolation; // 查看事務隔離級別
> select @@version; // 查看數據庫版本
> SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS; //  查詢鎖
複製代碼

02 場景分類

2.1 主鍵(有值)

說明:主鍵查詢,查詢數據存在。

執行悲觀鎖查詢:

select * from user where id = 1 for update;
複製代碼

執行更新操作,被鎖住了:

update user set user_name = "樓仔小弟" where id = 1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
複製代碼

查看鎖信息:

  • lock_mode 爲 X(排他鎖)
  • lock_type 爲 RECORD,行級鎖

結論:查詢條件爲主鍵,且有值,行鎖

2.2 主鍵(空值)

操作:主鍵查詢,查詢數據不存在。

執行悲觀鎖查詢:

select * from user where id = 2 for update;
複製代碼

執行插入操作,被鎖住了:

insert into user values(3, 14, '樓仔小弟', 28);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
複製代碼

這裏的間隙鎖,鎖住的區間是 id 字段的 (1,4) 區間,查看鎖信息:

  • lock_mode 爲 X(排他鎖)+ Gap(間隙鎖)
  • lock_type 爲 RECORD,行級鎖

結論:查詢條件爲主鍵,且空值,間隙鎖

2.3 唯一索引(有值)

說明:唯一索引查詢,數據存在。

執行悲觀鎖查詢:

select * from user where user_no = 10 for update;
複製代碼

執行更新操作,被鎖住了:

update user set user_name = "樓仔小弟" where user_no = 10;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
複製代碼

結論:查詢條件爲唯一索引,且有值,行鎖

2.4 唯一索引(空值)

說明:唯一索引查詢,數據不存在。

執行悲觀鎖查詢:

select * from user where user_no = 11 for update;
複製代碼

執行插入操作,被鎖住了:

insert into user values(3, 14, '樓仔小弟', 28);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
複製代碼

這裏的間隙鎖,鎖住的區間是 user_no 字段的 (1,4) 區間。

結論:查詢條件爲唯一索引,且空值,間隙鎖

2.5 普通索引(有值)

說明:普通索引,數據存在。

執行悲觀鎖查詢:

select * from user where age = 18 for update;
複製代碼

執行更新操作,被鎖住了:

update user set user_name = "樓仔小弟" where age = 18;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
複製代碼

執行插入操作,被鎖住了:

insert into user values(3, 14, '樓仔小弟', 20);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
複製代碼

這裏鎖住的是 age 字段的 [18, 28) 這區間。

結論:查詢條件爲普通索引,且有值,間隙鎖

2.6 普通索引(空值)

說明:普通索引,數據不存在。

執行悲觀鎖查詢:

select * from user where age = 19 for update;
複製代碼

執行插入操作,被鎖住了:

insert into user values(3, 14, '樓仔小弟', 20);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
複製代碼

這裏鎖住的是 age 字段的 (18, 28) 這區間。

結論:查詢條件爲普通索引,且空值,間隙鎖

2.7 索引(範圍查詢)

說明:這裏的索引,包括主鍵索引、唯一索引和普通索引。

執行悲觀鎖查詢:

select * from user where id > 1 for update;
複製代碼

執行插入操作,被鎖住了:

insert into user values(3, 14, '樓仔小弟', 20);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
複製代碼

這裏其實可以對 id = 1 的數據進行更新,對於其它數據,都被鎖住,鎖住的範圍是 id 字段的 (1, 4],(4, 8],(8, 正無窮) 區間。

結論:查詢條件爲索引,且是範圍查詢,間隙鎖。

2.8 無索引

執行悲觀鎖查詢:

select * from user where user_name = "樓仔" for update;
複製代碼

執行插入操作,被鎖住了:

insert into user values(3, 14, '樓仔小弟', 20);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
複製代碼

這裏明顯是鎖表了,但是爲什麼鎖的信息還是行鎖呢,知道的同學,可以私我哈~~

結論:查詢條件爲無索引,表鎖。

03 加鎖規則

3.1 規律總結

我們把上面的結論進行彙總:

總結如下規律:

  1. 當查詢條件爲主鍵和唯一索引,當有值時,是行鎖;
  2. 當查詢條件爲主鍵和唯一索引,當爲空值時,是間隙鎖;
  3. 當查詢條件爲普通索引,是間隙鎖;
  4. 當查詢條件爲索引,且爲範圍查詢,是間隙鎖;
  5. 當查詢條件無索引,是表鎖。

3.2 加鎖規則

那是否有一套加鎖規則呢?

爲了便於大家理解,我先普及 3 個概念:

  • Record Lock:行鎖
  • Gap Lock:間隙鎖,鎖定一個範圍,但不包含記錄本身
  • Next-Key Lock:行鎖 + 間隙鎖,左開右閉,比如(1,5]

其實 MySQL 大佬林曉斌在極客時間講過,後來也有很多博主轉發過他的加鎖規則,我直接把這套規則貼一下。

兩個“原則”:

  • 原則 1:加鎖的基本單位是 next-key lock,其中 next-key lock 是前開後閉區間;
  • 原則 2:查找過程中訪問到的對象纔會加鎖。

兩個“優化”:

  • 優化 1:索引上的等值查詢,給唯一索引加鎖的時候,next-key lock退化爲行鎖;
  • 優化 2:索引上的等值查詢,向右遍歷時且最後一個值不滿足等值條件的時候,next-key lock 退化爲間隙鎖。

3.3 分析一下

這裏我們結合上面的案例,來解讀這套加鎖規則。

針對我們前面總結的 5 條規律,我們先分析這兩條:

  • 當查詢條件爲主鍵和唯一索引,當有值時,是行鎖;
  • 當查詢條件爲主鍵和唯一索引,當爲空值時,是間隙鎖。

下面我們根據 “兩個原則” + “兩個優化” 來分析一下。

根據 “原則 1”,加鎖的基本單位是 next-key lock,當 “索引上爲等值查詢” 時(即能查到該數據),根據 “優化 1”,間隙鎖退化爲行鎖。

同理,根據 “優化 2”,索引上的等值查詢,向右遍歷時且最後一個值不滿足等值條件的時候,next-key lock 退化爲間隙鎖。

再分析這兩條:

  • 當查詢條件爲普通索引,是間隙鎖;
  • 當查詢條件爲索引,且爲範圍查詢,是間隙鎖;

同上,通過 “原則 1” 和 “優化 2”,普通索引 是加的間隙鎖。

對於範圍查詢,個人認爲上面的規則還不能完全覆蓋,當時林曉斌針對這些規則,舉了 4 個示例,然後進行詳細剖析,包括間隙鎖的範圍區間計算。

因爲篇幅原因,這裏就不再詳細展開,如果後續需要,我也可能會單獨出一篇。

04 寫在最後

最後我們再回顧一下(RR 隔離級別):

  1. 當查詢條件爲主鍵和唯一索引,當有值時,是行鎖;
  2. 當查詢條件爲主鍵和唯一索引,當爲空值時,是間隙鎖;
  3. 當查詢條件爲普通索引,是間隙鎖;
  4. 當查詢條件爲索引,且爲範圍查詢,是間隙鎖;
  5. 當查詢條件無索引,是表鎖。

至於間隙鎖的範圍,如何計算,本文沒有詳細闡述,但是上面的這些規則,就能基本滿足我們日常工作需要。

如果想知道間隙鎖區間如何計算,歡迎催更哈~~

寫文不易,家裏還要帶娃,又肝了一個週末,不過大家的支持,永遠是樓仔輸出的最大動力。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章