今天再來分析一個死鎖場景,下面開始真正的內容,
建表陳述句:
CREATE TABLE `tenant_config` ( `id` bigint(21) NOT NULL AUTO_INCREMENT, `tenant_id` int(11) NOT NULL, `open_card_point` int(11) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uidx_tenant` (`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4表中有一條初始化資料:
INSERT INTO `tenant_config` (`tenant_id`, `open_card_point`) VALUES (123,0);資料庫隔離級別:RC
兩條 insert,兩條 update
事務 1 和事務 2 陳述句一毛一樣,都是下面這樣:
INSERT INTO `tenant_config` ( `tenant_id`, `open_card_point`) VALUES (123,111111); UPDATE tenant_config SET open_card_point = 0 where tenant_id = 123;代碼的邏輯大概如下,先插入,如果有沖突則更新
try { insert(); } catch (DuplicateKeyException e) { update() }死鎖條件的程序如下
事務 1:
INSERT INTO `tenant_config` ( `tenant_id`, `open_card_point`) VALUES (123,111111); ERROR 1062 (23000): Duplicate entry '123' for key 'uidx_tenant'加鎖情況,對 uk 加 S 鎖,如下:
事務 2:
INSERT INTO `tenant_config` ( `tenant_id`, `open_card_point`) VALUES (123,111111); ERROR 1062 (23000): Duplicate entry '123' for key 'uidx_tenant'加鎖情況,對 uk 加 S 鎖,如下:
事務 1:
UPDATE tenant_config SET open_card_point = 0 where tenant_id = 123;對 uk 加 X 鎖,因為事務 2 獲取了 S 鎖,進入鎖等待
事務 2:
UPDATE tenant_config SET open_card_point = 0 where tenant_id = 123;同樣想對 uk 加 X 鎖,死鎖條件產生:事務 2 拿到了 S 鎖,想加 X 鎖,事務 1 拿到了 S 鎖,也想加 X 鎖,彼此都在等對方的 S 鎖,
這種情況是最簡單的,如果只是這么簡單,我就不會寫了,哈哈,下面來看第二種情況,
一條 insert,兩條 update
第一步:事務 1,插入唯一鍵沖突
begin; INSERT INTO `tenant_config` ( `tenant_id`, `open_card_point`) VALUES (123,111111); ERROR 1062 (23000): Duplicate entry '123' for key 'uidx_tenant'第二步:事務 2
begin; UPDATE tenant_config SET open_card_point = 0 where tenant_id = 123 and 1 =1;第三步:事務 1
UPDATE tenant_config SET open_card_point = 0 where tenant_id = 123 and 1 =1;出現:事務 2 死鎖
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction分析程序如下:
事務 1
INSERT INTO `tenant_config` ( `tenant_id`, `open_card_point`) VALUES (123,111111);對 uk 加 S 鎖,這個沒有什么歧義,
接下來事務 2
UPDATE tenant_config SET open_card_point = 0 where tenant_id = 123 and 1 =1;這個對 ux 加 X 鎖,進入鎖等待狀態,這個也沒有什么問題,
接下來,事務 1 執行 update,情況就復雜很多了,也是想獲取 X 鎖,但是沒有那么順利,
進入死鎖檢測流程,重點代碼在lock_deadlock_occurs()函式,最近會進入 lock_deadlock_recursive()遞回呼叫函式,
-
start 表示頂層呼叫該函式的事務指標,比如現在正在執行的事務 1 就是 start
-
wait_lock 表示想要獲取的鎖,這里是事務 1 對 uk 的 X 鎖,
-
trx 等待鎖的事務指標
死鎖的本質是:在遞回程序中,如果沖突出現的鎖事務id等于頂層事務id(lock_trx == start),則說明有環,就發生死鎖,
以下記事務 1 為 t1,事務 2 為 t2
第一次遞回
wait_lock 屬于 t1 的 lock_X,就是 t1 update 想獲取的 X 鎖
這個時候會檢查記錄上所有的鎖,第一個鎖是 t1 事務的 S 鎖,第二個鎖是 t2 事務等待狀態的 X 鎖
檢查第一把鎖,t1 事務的 S 鎖,因為與 wait_lock 屬于同一個事務,沒有沖突,繼續檢查第二把鎖,
檢查第二把鎖,是 t2 事務處于等待狀態的 X 鎖,是互斥的,而且 t2 的 X 鎖是處于等待狀態的,開始第二次遞回呼叫,檢查 t2 的 X 鎖,查看它在等待什么鎖,
第二次遞回
此時傳入的 start 沒變,wait_lock 變為了 t2 的 X 鎖,也就是把 t2 的 X 鎖拿出來檢測,看跟現有鎖有哪些依賴,
t2 的 X 鎖在等待 t1 的 S 鎖,lock_trx 等于 start,成環死鎖產生,
也就是:t1 的 insert 插入加了 S 鎖,t2 的 X 鎖雖然沒加成功,但是真實存在,標記為等待狀態,t1 再想獲取 X 鎖,發現與 t2 等待狀態的 X 鎖沖突,再次檢測,發現 t2 等待狀態的 X 鎖與 t1 的 S 鎖沖突,死鎖產生,
我畫了一個圖方便你理解:
后記
死鎖分析是比較復雜的,除錯原始碼可以比較清晰的理清思路,上面是我除錯原始碼的一些結論,如果有理解有誤的地方,記得及時幫我指出,
看完三件事??
如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:
-
點贊,轉發,有你們的 『點贊和評論』,才是我創造的動力,
-
關注公眾號 『 java爛豬皮 』,不定期分享原創知識,
-
同時可以期待后續文章ing??
作者:挖坑的張師傅
出處:https://club.perfma.com/article/1982680
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/226049.html
標籤:Java
