天天看點

兩個小工具,MySQL死鎖分析,新技能又Get!!!

資料庫死鎖,是最難調試與追蹤的。

場景如下:

兩個小工具,MySQL死鎖分析,新技能又Get!!!

同一個表,事務内先插入一條記錄,再更新這條記錄,并發時會死鎖。

兩個小工具,MySQL死鎖分析,新技能又Get!!!

并且能夠複現。

可以通過什麼工具模拟并發事務,檢視資訊,解決問題呢?這是今天要分享的内容。

一、前置準備

set session transaction isolation level repeatable read;

set session autocommit=0;

create table t (

id int(20) primary key AUTO_INCREMENT,

cell varchar(20) unique

)engine=innodb;

start transaction;

insert into t(cell) values(11111111111);

insert into t(cell) values(22222222222);

insert into t(cell) values(33333333333);

commit;

說明:

(1)案發時,事務隔離級别RR;

(2)多終端實驗,需要關閉事務自動送出;

(3)建表,設定PK與unique,初始化資料;

二、并發事務模拟

Session A:

insert into t(cell)values(44444444444);  [1]

        Session B:

        start transaction;

        insert into t(cell) values(55555555555); [2]

update t set cell=123 where cell=44444444444; [3]   

        update t set cell=456 where cell=55555555555; [4]

開啟兩個終端模拟并發事務:

(1)紅色SQL為事務A;

(2)黑色SQL為事務B;

(3)13為執行時序;

三、實驗現象

事務A插入資料,最先執行

結果:插入成功

insert into t(cell) values(55555555555); [2]

事務B插入資料,第二執行

結果:插入成果

update t set cell=123 where cell=44444444444; [3]

事務A修改[1]中插入的資料,第三執行

結果:阻塞,等待執行結果

兩個小工具,MySQL死鎖分析,新技能又Get!!!

畫外音:修改一條自己插入的資料,在等待什麼呢?

update t set cell=456 where cell=55555555555; [4]

事務B修改[2]中插入的資料,最後執行

結果:

(1)事務B死鎖,事務B被復原

兩個小工具,MySQL死鎖分析,新技能又Get!!!

(2)事務A中,[3]語句阻塞結束,執行成功

兩個小工具,MySQL死鎖分析,新技能又Get!!!

畫外音:說明事務A中阻塞的語句,确實在等事務B中的某個鎖。

四、結果分析

兩個事務,各自修改自己插入的資料,卻産生了死鎖,确實詭異。

上述實驗現象的兩個核心問題是:

(1)語句[3]阻塞,在等待什麼鎖?

(2)語句[4]死鎖,此時事務A和事務B一定是彼此占住一把鎖,請求彼此的鎖,這些鎖又是什麼呢?

工具一:

show engine innodb status;

畫外音:前文《超贊,InnoDB調試死鎖的方法!》就詳細分享過,InnoDB死鎖的分析實踐。

執行之後,顯示的内容如下(放大仔細看):

兩個小工具,MySQL死鎖分析,新技能又Get!!!

資訊很多,别急,樓主娓娓道來。

第一部分,關鍵詞是:

(1)Transaction 1,事務3998;

(2)在執行

update t set cell=123 where cell=44444444444;

(3)正在等待鎖釋放(waiting for this lock to be granted),記錄鎖(record locks),主鍵索引上(index primary),互斥鎖(lock_mode X),實體記錄(physical record),asc 55555555555;

畫外音:英文比較差沒事,抓關鍵詞。

畫外音,InnoDB存儲引擎,聚集索引與非聚集索引的實作方式,決定了鎖會加在聚集索引上。

第二部分,關鍵詞是:

(1)Transaction 2,事務3999;

(2)正在執行

update t set cell=456 where cell=55555555555;

(3)持有鎖(holds the lock),記錄鎖(record locks),主鍵索引上(index primary),互斥鎖(lock_mode X),實體記錄(physical record),asc 55555555555;

(4)正在等待鎖釋放(waiting for this lock to be granted),記錄鎖(record locks),主鍵索引上(index primary),互斥鎖(lock_mode X),實體記錄(physical record),asc 11111111111;

(5)事務2復原(we roll back transaction 2);

通過show engine innodb status; 能夠看到很多事務與鎖之間的資訊,對分析問題十分有幫助,這些資訊,能夠解釋一些問題,但仍有兩個疑惑:

(1)事務1為啥想拿55555555555的鎖?

畫外音:這正是,事務1被阻塞的原因。

(2)事務2為啥想拿11111111111的鎖?死鎖的發生,說明事務1此時真占着11111111111的鎖,這又是為什麼呢?

畫外音:第一個事務占111搶555,第二個事務占555搶111,循環嵌套,才會死鎖。

工具二:

explain

為了進一步尋找原因,可以通過explain看下導緻死鎖語句的執行計劃。

explain update t set cell=456 where cell=55555555555;

兩個小工具,MySQL死鎖分析,新技能又Get!!!

:SIMPLE

這是一個簡單類型的SQL語句,不含子查詢或者UNION。

type:index

通路類型,即找到所需資料使用的周遊方式,潛在的方式有:

(1)ALL(Full Table Scan):全表掃描;

(2)index:走索引的全表掃描;

(3)range:命中where子句的範圍索引掃描;

(4)ref/eq_ref:非唯一索引/唯一索引單值掃描;

(5)const/system:常量掃描;

(6)NULL:不用通路表;

上述掃描方式,ALL最慢,逐漸變快,NULL最快。

懷疑點1:明明cell字段有uniq索引,為何要進行走PK索引的全表掃描呢?

possible_keys:NULL

可能在哪個索引找到記錄。

key:PRIMARY

實際使用索引。

畫外音:使用PK進行的全表掃描。

ref:NULL

哪些列,或者常量用于查找索引上的值。

懷疑點2:where條件中的查詢條件55555555555,本來應該作為在索引上被檢索的值呀?

rows:5

找到所需記錄,預估需要讀取的行數。

懷疑點3:明明修改的是5,為何初始化的1,2,3,以及第一個事務插入的4,以及第二個事務插入的5,都要被讀取呢?不應該全表掃描呀。

通過explain,基本已經可以判斷:

并沒有和我們預想一樣,走cell索引進行查詢,而是走了PK索引進行了全表掃描。

再仔細一看:

建表的時候cell定義的是字元串類型。

而更新的時候,

使用的是整數類型。

類型轉換,會導緻全表掃描,出現鎖更新,鎖住全部記錄。

加上引号,再次通過explain驗證一下:

explain update t set cell= '456 ' where cell= '55555555555 ';

兩個小工具,MySQL死鎖分析,新技能又Get!!!

果然印證了猜想:

(1)type:range,變為了走索引的字元串比對,範圍掃描;

(2)possible_keys:cell,通過cell索引找到了記錄;

(3)key:cell,實際使用cell索引;

(4)ref:const,使用了常量' 555'進行比對;

(5)rows:1,預估讀取行數是1;

這下全部可以解釋了

兩個小工具,MySQL死鎖分析,新技能又Get!!!

總結

就本例而言:需要注意字元串與整數之間的強制類型轉換,有時候少一個引号,就會使得行鎖更新為表鎖。

死鎖是MySQL中非常難調試的問題,常見的思路與方法有:

(1)通過多終端模拟并發事務,複現死鎖;

(2)通過show engine innodb status; 可以檢視事務與鎖的資訊;

(3)通過explain可以檢視執行計劃;

思路比結論更重要,希望大家有收獲。

本文轉自“架構師之路”公衆号,58沈劍提供。