近期寫了不少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%」;
事務自動提交
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的鎖情況,也可以除錯死鎖
mysql innodb死鎖問題詳解
1 最近旅遊電商平台對外提供的介面經常有終端使用者反映請求超時異常 2 進過排查伺服器日誌有報錯,錯誤資訊如下 error 1205 hy000 lockwait timeout exceeded try restarting transaction 3 了解平台使用的是mysql 資料庫版本5.6...
Mysql InnoDB儲存引擎中 死鎖
今天我們來看死鎖,死鎖的一般場景大家都能想到,只要你不是很菜,a獲取資源z之後再獲取資源x,b獲取資源x之後再獲取資源z,這樣就造成了死鎖。解釋 死鎖是指兩個或兩個以上的事務在執行過程中,因爭奪鎖資源而造成的一種互相等待的現象。解決辦法 1.超時。innodb中設定了超時時間,引數為innodb l...
簡單理解mysql InnoDB的死鎖問題
mysql三種鎖的級別 行級鎖 開銷大,加鎖慢 會出現死鎖 鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高 表級鎖 開銷小,加鎖快 不會出現死鎖 鎖定粒度大,發生鎖衝突的概率最高,併發度最低 頁面鎖 開銷和加鎖時間界於表鎖和行鎖之間 會出現死鎖 鎖定粒度界於表鎖和行鎖之間,併發度一般 資料庫儲存引...