InnoDB除錯死鎖的方法
近期寫了不少InnoDB鎖相關的文章,不少小夥伴問,如何在MySQL終端模擬併發事務,如何復現之前文章中的案例。今天,咱們一起動起手來,模擬併發事務的互斥與死鎖。
【事前準備】
- 安裝MySQL服務端
- 安裝MySQL客戶端
- 安裝能夠模擬多個併發事務的終端
畫外音:樓主使用的是MySQL5.6,官方客戶端mysql,模擬併發終端用的SecureCRT。
【配置的確認與修改】
要測試InnoDB的鎖互斥,以及死鎖,有幾個配置務必要提前確認:
- 區間鎖是否關閉
- 事務 自動提交 (auto commit)是否關閉
- 事務的 隔離級別 (isolation level)
這幾個引數,會影響實驗結果。
【事務的隔離級別】輔助材料:
間隙鎖是否關閉
區間鎖(間隙鎖,臨鍵鎖)是InnoDB特有施加在索引記錄區間的鎖,MySQL5.6可以手動關閉區間鎖,它由innodb_locks_unsafe_for_binlog引數控制:
- 設定為ON,表示關閉區間鎖,此時一致性會被破壞(所以是unsafe)
- 設定為OFF,表示開啟區間鎖
可以這麼查詢該引數:
show global variables like “innodb_locks%”;
【間隙鎖,臨鍵鎖】輔助材料:
《 InnoDB,索引記錄上的三種鎖 》
事務自動提交
MySQL預設把每一個單獨的SQL語句作為一個事務,自動提交。
可以這麼查詢事務自動提交的引數:
show global variables like “autocommit”;
事務的隔離級別
不同事務的隔離級別,InnoDB的鎖實現是不一樣。
可以這麼查詢事務的隔離級別:
show global variables like “tx_isolation”;
可以這麼設定事務的隔離級別:
set session transaction isolation level X;
X取:
read uncommitted
read committed
repeatable read
serializable
這三個引數,MySQL5.6的預設值如上:
- OFF,表示使用區間鎖
- On,表示事務自動提交
- RR,事務隔離級別為可重複讀
要模擬併發事務,需要 修改 事務自動提交這個選項,每個session要改為手動提交。
任何連上MySQL的session,都要手動執行:
set session autocommit=0;
以手動控制事務的提交。
如上圖,需要把session的autocommit設定為OFF。
可以看到,修改session變數,並不影響global變數,全域性其他的session仍然是ON。
畫外音:session變數預設繼承global變數,也可以單獨修改。
【資料準備】
InnoDB的行鎖都是實現在索引上的,實驗可以使用主鍵,建表時設定為innodb引擎:
create table t (
id int(10) primary key
)engine=innodb;
插入一些實驗資料:
start transaction;
insert into t values(1);
insert into t values(3);
insert into t values(10);
commit;
這是實驗的初始狀態,不同實驗開始之初,都預設回到初始狀態。
【實驗一,間隙鎖互斥】
開啟區間鎖,RR的隔離級別下,上例會有:
(-infinity, 1)
(1, 3)
(3, 10)
(10, infinity)
這四個區間。
事務A刪除某個區間內的一條不存在記錄,獲取到共享間隙鎖,會阻止其他事務B在相應的區間插入資料,因為插入需要獲取排他間隙鎖。
session A:
set session autocommit=0;
start transaction;
delete from t where id=5;
session B:
set session autocommit=0;
start transaction;
insert into t values(0);
insert into t values(2);
insert into t values(12);
insert into t values(7);
事務B插入的值:0, 2, 12都不在(3, 10)區間內,能夠成功插入,而7在(3, 10)這個區間內,會阻塞。
可以使用:
show engine innodb status;
來檢視鎖的情況。
如上圖,可以看到(請把圖放大):
insert into t values(7);
正在等待共享間隙鎖的釋放。
如果事務A提交或者回滾,事務B就能夠獲得相應的鎖,以繼續執行。
如果事務A一直不提交,事務B會一直等待,直到超時,超時後會顯示:
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
【實驗二,共享排他鎖死鎖】
回到資料的初始狀態,這次需要三個併發的session。
畫外音:SecureCRT得開三個視窗了。
session A先執行:
set session autocommit=0;
start transaction;
insert into t values(7);
session B後執行:
set session autocommit=0;
start transaction;
insert into t values(7);
session C最後執行:
set session autocommit=0;
start transaction;
insert into t values(7);
三個事務都試圖往表中插入一條為7的記錄:
(1)A先執行,插入成功,並獲取id=7的排他鎖;
(2)B後執行,需要進行PK校驗,故需要先獲取id=7的共享鎖,阻塞;
(3)C後執行,也需要進行PK校驗,也要先獲取id=7的共享鎖,也阻塞;
如果此時,session A執行:
rollback;
id=7排他鎖釋放。
則B,C會繼續進行主鍵校驗:
(1)B會獲取到id=7共享鎖,主鍵未互斥;
(2)C也會獲取到id=7共享鎖,主鍵未互斥;
B和C要想插入成功,必須獲得id=7的排他鎖,但由於雙方都已經獲取到id=7的共享鎖,它們都無法獲取到彼此的排他鎖,死鎖就出現了。
當然,InnoDB有死鎖檢測機制,B和C中的一個事務會插入成功,另一個事務會自動放棄:
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
【實驗三,併發間隙鎖的死鎖】
共享排他鎖,在併發量插入相同記錄的情況下會出現,相應的案例比較容易分析。而併發的間隙鎖死鎖,是比較難定位的。
回到資料的初始狀態,這次需要兩個併發的session,其SQL執行序列如下:
A:set session autocommit=0;
A:start transaction;
A:delete from t where id=6;
B:set session autocommit=0;
B:start transaction;
B:delete from t where id=7;
A:insert into t values(5);
B:insert into t values(8);
A執行delete後,會獲得(3, 10)的共享間隙鎖。
B執行delete後,也會獲得(3, 10)的共享間隙鎖。
A執行insert後,希望獲得(3, 10)的排他間隙鎖,於是會阻塞。
B執行insert後,也希望獲得(3, 10)的排他間隙鎖,於是死鎖出現。
仍然使用:
show engine innodb status;
來檢視死鎖的情況。
事務1佔有什麼鎖,請求什麼鎖;事務2佔有什麼鎖,請求什麼鎖,一清二楚(請把圖放大)。
另外,檢測到死鎖後,事務2自動回滾了:
WE ROLL BACK TRANSACTION (2)
事務1將會執行成功。
【總結】
說了很多,希望大家能起手來,這樣對InnoDB鎖的機制,以及鎖的除錯印象會更加深刻:
- 併發事務,間隙鎖可能互斥
(1)A刪除不存在的記錄,獲取共享間隙鎖;
(2)B插入,必須獲得排他間隙鎖,故互斥;
- 併發插入相同記錄,可能死鎖(某一個回滾)
- 併發插入,可能出現間隙鎖死鎖(難排查)
- show engine innodb status; 可以檢視InnoDB的鎖情況,也可以除錯死鎖