MySQL鎖(四)行鎖的加鎖規則和案例

在上一篇文章,咱們學習了間隙鎖和next-key lock,可是不知道怎麼加鎖,有哪些規則。間隙鎖的概念不太好理解,尤爲是配合上行鎖後,很容易在判斷是否會出現鎖等待的問題上犯錯。mysql

今天咱們就來學習一下加鎖規則吧。sql

在學習前要說明一點,如下的規則只限於版本範圍:5.x系列<=5.7.24,8.0系列<=8.0.13。學習

加鎖規則

這個加鎖規則包含兩個「原則」、兩個「優化」和一個「bug」。優化

  1. 原則1:加鎖的基本單位是next-key lock。但願你還記得,next-key lock是前開後閉區間。
  2. 原則2:查找過程當中訪問到的對象纔會加鎖。
  3. 優化1:索引上的等值查詢,給惟一索引加鎖的時候,next-key lock退化爲行鎖。
  4. 優化2:索引上的等值查詢,向右遍歷時且最後一個值不知足等值條件的時候,next-key lock退化爲間隙鎖。
  5. 一個bug:惟一索引上的範圍查詢會訪問到不知足條件的第一個值爲止。

下面以表t爲例來介紹一下這些規則。表t的建表語句和初始化語句以下。code

CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `c` int(11) DEFAULT NULL,
  `d` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `c` (`c`)
) ENGINE=InnoDB;

insert into t values(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);

案例1:等值查詢間隙鎖


圖1 等值查詢間隙鎖

分析:對象

  • 步驟1:根據原則1,加鎖(5, 10]
  • 步驟2:根據優化2,id=10不知足查詢條件,所以退化爲間隙鎖(5, 10)

結論:blog

  • Session B阻塞是由於id=8在間隙鎖(5, 10)內
  • Session C能夠執行是由於沒有鎖住id=10這行

案例2:非惟一索引等值鎖


圖2 非惟一索引等值鎖

分析:排序

  • 步驟1:根據原則1,加鎖(0, 5]
  • 步驟2:因爲c是普通索引,所以還須要繼續遍歷,直到找到c=10,不知足條件。根據原則2,訪問到的對象要加鎖,所以要加(5, 10]。
  • 步驟3:同時根據優化2,這是一個等值查詢,向右遍歷的不知足條件的第一個值10,(5, 10]要退化爲間隙鎖(5, 10)
  • 所以加鎖是索引c的next-key lock(0,5]和間隙鎖(5,10)

結論:索引

  • Session B能夠執行是由於加鎖的是索引c,而不是主鍵索引
  • Session C阻塞是由於c的插入值是7,在間隙鎖(5, 10)範圍內

案例3:主鍵索引範圍鎖


圖3 主鍵索引範圍鎖

分析:get

  • 步驟1:根據原則1,加鎖(5, 10]
  • 步驟2:根據優化1,退化爲id=10的行鎖
  • 步驟3:繼續遍歷,找到不知足id<11的值id=15,加鎖(10, 15]

所以加鎖id=15的行鎖和id的next-key lock(10, 15]

結論:

  • 插入id=13被阻塞:next-key lock (10, 15]
  • 更新id=15被阻塞:next-key lock (10, 15]

案例4:非惟一索引範圍鎖


圖4 非惟一索引範圍鎖

分析:

  • 步驟1:根據原則1,加鎖(5, 10]
  • 步驟2:繼續遍歷,找到不知足c<11的值c=15,根據原則2,加鎖(10, 15]

所以加鎖索引c (5, 10]和(10, 15]

結論:

  • 插入c=8,被(5, 10]阻塞
  • 更新c=15,被(10, 15]阻塞

案例5:惟一索引範圍鎖bug


圖5 惟一索引範圍鎖bug

分析:

  • 步驟1:根據原則1,加鎖(10, 15],再根據優化1,退化爲id=15的行鎖
  • 步驟2:向右遍歷,加鎖(10, 15]
  • 步驟3:根據BUG,要訪問到不知足條件的第一個值,即id=20,加鎖(15 ,20]。

所以加鎖爲(10, 15]和(15, 20]

結論:

  • 更新id=20阻塞,被(15, 20]鎖住
  • 插入id=16阻塞,被(15, 20]鎖住

案例6:非惟一索引上存在"等值"的例子

mysql> insert into t values(30,10,30);

圖6 非惟一索引上存在"等值"的例子

分析:

  • 步驟1:根據原則1,(c=5,id=5)到(c=10,id=10)這個next-key lock
  • 步驟2:向右查找,直到碰到(c=15,id=15)這一行,循環才結束。根據優化2,這是一個等值查詢,向右查找到了不知足條件的行,因此會退化成(c=10,id=10) 到 (c=15,id=15)的間隙鎖

所以加鎖(c=5,id=5)到(c=10,id=10)這個next-key lock和(c=10,id=10)到(c=15,id=15)這個間隙鎖

結論:

  • 插入c=12阻塞,被(c=10,id=10)到(c=15,id=15)這個間隙鎖鎖住
  • 更新c=15成功,沒有鎖住c=15

案例7:limit 語句加鎖

先插入一條記錄。

mysql> insert into t values(30,10,30);

圖7 limit 語句加鎖

分析:

  • 步驟1:根據原則1,(5, 10],由於c=10有兩條行,所以遍歷到這裏就結束
  • 步驟2:由於是delete,所以加兩個行鎖(id=10和id=30)

所以加鎖c (5, 10)和兩個行鎖(id=10和id=30)

結論:

  • 插入c=12成功,由於c=12沒有被鎖住

說明:這個例子對咱們實踐的指導意義就是,在刪除數據的時候儘可能加limit。

案例8:一個死鎖的例子

這個案例的目的是說明:next-key lock其實是間隙鎖和行鎖加起來的結果。


圖8 一個死鎖的例子

分析:

  • 步驟1:根據原則1,加鎖(5, 10]
  • 步驟2:繼續遍歷,直到c=15不知足條件,加鎖(10, 15],根據優化2,退化爲(10, 15)

結論:

  • Session B在等待鎖,此時Session B已經加了間隙鎖(5, 10),在等待加行鎖c=10。
  • Session A插入c=8,也在等待鎖,從而致使死鎖

說明:next-key lock具體執行的時候,是要分紅間隙鎖和行鎖兩段來執行的。

案例9:非惟一索引排序範圍鎖


圖9 非惟一索引排序範圍鎖

分析:

  • 步驟1:先執行c=20,加鎖(15, 20]
  • 步驟2:根據優化2,加間隙鎖(20, 25)
  • 步驟3:再執行c=15,加鎖(10, 15]
  • 步驟4:繼續向左遍歷,找到記錄id=10爲止,加鎖(5, 10]

在掃描過程當中,c=20、c=1五、c=10這三行都存在值,因爲是select *,因此會在主鍵id上加三個行鎖。

所以要加鎖索引c (5, 25)和三個行鎖(id=10,id=15,id=20)。

結論:

  • 插入c=6,被c(5, 25)鎖住

案例10:不等號條件裏的等值查詢

begin;
select * from t where id>9 and id<12 order by id desc for update;

在執行過程當中,經過樹搜索的方式定位記錄的時候,用的是「等值查詢」的方法。

分析:

  • 步驟1:根據原則1,加鎖 (10, 15]
  • 步驟2:根據優化2,退化爲(10, 15)
  • 步驟3:向左遍歷,找到id=10,加鎖(5, 10],繼續找到id=5爲止,加鎖(0, 5]

案例11:in範圍鎖

begin;
select id from t where c in(5,20,10) lock in share mode;

分析:

說明:鎖是逐個逐個加的。

  • 步驟1:先c=5,加(0, 5]和(5, 10)
  • 步驟2:再c=10,加(5, 10]和(10, 15)
  • 步驟3:後c=20,加(15, 20]和(20, 25)

間隙鎖是不互斥的,由於加鎖範圍是(0, 25),除c=15外。

死鎖狀況

select id from t where c in(5,20,10) order by c desc for update;

有一種狀況,同時執行倒序語句,由於恰好同時執行,逐漸加鎖(倒序加鎖),會出現死鎖狀況。

參考資料

相關文章
相關標籤/搜索