Mysql加鎖與實踐

雨季不再來發表於2018-10-19

1. 鎖分類

innodb中的鎖分為S鎖,即共享鎖,另一種為X鎖,排它鎖,比如:

共享鎖(S)

select * from supplier where id=5 lock in share mode;
複製程式碼

排他鎖(X)

select * from supplier where id=5 for update;
複製程式碼

或者insert,delete,update語句,這都是排他鎖

相容性

這兩種鎖的相容如下:

X S
X N N
S N Y

意向鎖

可以理解為屬於S鎖和X鎖的父節點,即要獲取S鎖或者X鎖的話,必須先提前獲取意向鎖,即IS或者IX鎖,那綜合起來看下,這兩類鎖的相容情況如下

X S IX IS
X N N N N
S N Y N Y
IX N N Y Y
IS N Y Y Y

隱式鎖

也是醉了,為什麼要搞這麼多概念。。這種鎖,可以認為不衝突的時候不加鎖,這個時候的鎖就是隱式鎖,等遇到衝突了,該鎖會升級為顯示鎖,還是通過一個例子來說明吧:

Mysql加鎖與實踐
事務1(其中age是普通索引)

select * from test01 where age=21 for update;
複製程式碼

我們看到mysql中其實是沒有找到鎖的:

Mysql加鎖與實踐

事務2

insert into test01(id,name,age) values(8,'zzh',22);
複製程式碼

這個時候由於[21,23)有gap鎖,所以事務2會被阻塞住,這個時候再看mysql中的鎖記錄

Mysql加鎖與實踐
可以看到一開始事務1雖然使用的是...for update,但是由於沒有衝突所以加的是隱式鎖,等到事務2開始導致有衝突存在,所以事務1的鎖改為現實鎖。

2.加鎖分析與實踐

具體在事務的不同隔離級別下,不同的場景加鎖分析,在hedecheng加鎖分析這篇文章已經講解的非常詳細了,這裡就不再多說了。主要說下其他情況下總結的一些加鎖分析。

2.1 插入意向鎖(Insert Intention Locks)

先來看官網對該鎖的解釋

Mysql加鎖與實踐
我們重點關注下紅色的區域:在遇到衝突的時候,會在該索引所在的位置加S鎖,而該共享鎖又很容易導致死鎖,官網中就有個例子來專門說明這種共享鎖導致的死鎖,即三個事務同事執行一條插入語句其中一個事務回滾導致死鎖:

Mysql加鎖與實踐
我們可以簡單的把這種情況模仿一遍:

Mysql加鎖與實踐
事務1

 insert into test01(id,name,age)values(12,"zzh",33);
複製程式碼

事務2

 insert into test01(id,name,age)values(12,"zzh",33);
複製程式碼

事務3

 insert into test01(id,name,age)values(12,"zzh",33);
複製程式碼

如下圖操作:

Mysql加鎖與實踐
我們看下Mysql中鎖的記錄

Mysql加鎖與實踐
其中這個就印證了在意向鎖衝突的時候,請求加的是S鎖,然後我們回滾事務1

事務1

rollback
複製程式碼

Mysql加鎖與實踐
這個時候我們看到事務2成功了,事務3出現死鎖

Mysql加鎖與實踐
我們可以從死鎖日誌中看到:

  • 事務2:等待鎖模式為X的插入意向鎖
  • 事務3:等待鎖模式為X的插入意向鎖;擁有鎖模式為S的record lock
  • 暗含條件:事務2也擁有鎖模式為S的record lock,這才導致了死鎖,死鎖日誌會把最終成功獲取鎖了的事務已經擁有的鎖不會列印出來

所以存在 事務2->事務3,事務3->事務2 死鎖發生

官網中還要另一個類似的例子,這裡就不多做分析了,原因類似。

下面我們看下另一種情況:

Mysql加鎖與實踐
事務1(事務id=2944)

select * from test01 where age=21 for update;
複製程式碼

事務2(事務id=2945)

insert into test01(id,name,age)values(2,"zzh",22);
複製程式碼

事務3(事務id=2946)

select * from test01 where age=21 lock in share mode;
複製程式碼

執行如下

Mysql加鎖與實踐
mysql中鎖記錄

Mysql加鎖與實踐
我們可以看到

  • 事務1:在primary key的(X,RECORD LOCK),在age_idx的(X,RECORD LOCK)
  • 事務2:在primay key 的(S,RECORD LOCK),這個就是我們前面講到的,事務2在獲取插入意向鎖出現衝突,所以阻塞在了要獲取(S,RECORD LOCK)
  • 事務3:在age_idx的(S,RECORD LOCK),

然後我們提交事務1 事務1

commit;
複製程式碼

Mysql加鎖與實踐

  • 事務2:加S鎖成功,但是出現唯一鍵衝突,直接報錯,這裡有個很重要的一點是,在事務對該行成功加S鎖之後發現記錄存在的情況下,會直接報錯,但是雖然報錯,S鎖不會釋放
  • 事務3:獲取S鎖成功,但是是在age_idx上,跟事務2不一樣。

我們再重新執行事務1

事務1(事務id=29467)

select * from test01 where age=21 for update;
複製程式碼

執行結果被阻塞:

Mysql加鎖與實踐
我們看下mysql中的鎖記錄:
Mysql加鎖與實踐
可以看到事務3擁有在age_idx的S鎖,阻塞了事務1要獲取的X鎖,那這個時候我們提交事務3 事務3 commit;

Mysql加鎖與實踐
我們發現事務1還處於阻塞的狀態,看下mysql中的鎖記錄

Mysql加鎖與實踐
這個時候明白了,事務2雖然執行失敗,但是其由於插入意向鎖衝突所加的S鎖並未釋放,所以會導致事務1還處於阻塞中。只有當我們提交了事務2,事務1才會真正執行。

2.2 select...for update

該語句加鎖分為兩種情況

  • 記錄存在:如果是RC模式下,加的是(X,RECORD LOCK),RR 模式下,加的是(X,NEXT-KEY LOCK),該鎖相互不相容
  • 記錄不存在:加(X,GAP)鎖,並且該鎖是相容的,但是與Insert Intention Locks 不相容,這個很容易導致死鎖

2.2.1 不同索引之間等待出現死鎖

Mysql加鎖與實踐
事務1

select * from test01 where age=21 for update;
複製程式碼

事務2

insert into test01(id,name,age)values(3,"zzh",22);
複製程式碼

Mysql加鎖與實踐

Mysql加鎖與實踐

事務1

insert into test01(id,name,age)values(3,"zzh",100);
複製程式碼

按如上順序執行結果:

Mysql加鎖與實踐
我們發現出現死鎖,事務2被mysql回滾之後事務1執行成功。我們看下具體的死鎖日誌

Mysql加鎖與實踐
通過死鎖日誌我們可以分析出來

  • 事務1: 擁有age_idx(X,NEXT-KEY LOCK),加插入意向鎖存在衝突,所以等待id=3位置的(S,RECORD LOCK)
  • 事務2: 等待age_idx的插入意向鎖(插入意向鎖不只是在primary key上才有)
  • 暗含條件:事務2雖然在age_idx上等待插入意向鎖,但是在id=3位置上加插入意向鎖成功了,這才有了事務1在該位置加插入意向鎖存在衝突

2.2.2 記錄不存在導致的死鎖

Mysql加鎖與實踐
事務1(事務id=29684)

select * from test01 where age=21 for update;
複製程式碼

事務2(事務id=29683)

select * from test01 where age=21 for update;
複製程式碼

事務1

insert into test01(id,name,age)values(3,"zzh",22);
複製程式碼

執行結果如下

Mysql加鎖與實踐

Mysql加鎖與實踐
可以看到

  • 事務1和事務2同時持有了age=22這個記錄的gap鎖,由於記錄不存在,所以此時的gap鎖相容
  • 但是記錄不存在相容的gap鎖和插入意向鎖兵不相容,所以事務1向age=22索引所請求的插入意向鎖會等待。

事務2

insert into test01(id,name,age)values(3,"zzh",22);
複製程式碼

Mysql加鎖與實踐

由上面的分析同樣得出事務2的等待有兩種情況:

(1) 和事務1一樣,等待age=22的插入意向鎖,此時發現已經有事務1在等待該位置的插入意向鎖,那就等待該位置的S鎖

(2) 事務1雖然在age=22的插入意向鎖等待,但是id=3的插入意向鎖是加成功了,所以如果事務2在id=3這裡等待插入意向鎖的話,也會有衝突,那就等待該位置的S鎖

所以,有這兩種情況都會導致事務出現死鎖,我們具體看下死鎖日誌:

Mysql加鎖與實踐
我們看到:

  • 事務1在等待age=22位置的插入意向鎖
  • 事務2在等待 id=3位置的S鎖,也就是我們分析的第二種情況,同時事務2擁有age=22位置的gap鎖

我們上面有分析,事務2的等待應該是有兩種情況,而出現這兩種情況的可能就是事務2的這條語句

insert into test01(id,name,age)values(3,"zzh",22);
複製程式碼

我們瞭解到在記錄不存在的時候,如果對此記錄進行select...for update語句,該語句會對空位置加gap鎖,這個會比較危險,如果我們的表用的是自增主鍵,而此時如果查詢一個不存在的記錄,那會把未來要插入的所有的空隙都加了gap鎖,會導致以後表中無法在插入任何資料,這個極其危險。

3. 小結

本篇,我們介紹了mysql中的鎖以及在實踐中可能遇到的一些死鎖,專門通過幾個demo集中分析了一下,對於加鎖的分析,在文中所提到的hedecheng的文章中,已經有很深的講解,所以本文並沒有對此總結。重點還是通過一些實際中用到的例子進行實際的分析一下整個過程。重點是插入意向鎖和select...for update中的一些加鎖的分析。下一篇來專門介紹下mysql的MVCC機制

相關文章