讀者反饋了一個死鎖案例,比較有意思,我分析總結了一篇文章。java
須要一些基礎,下面是我在掘金上寫的另外五篇調試源碼分析鎖的文章,能夠順便看看:數據庫
下面開始真正的內容:spa
建表語句:3d
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
事務 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 鎖。
這種狀況是最簡單的,若是隻是這麼簡單,我就不會寫了,哈哈,下面來看第二種狀況。
第一步:事務 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()
遞歸調用函數。
死鎖的本質是:在遞歸過程當中,若是衝突出現的鎖事務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 鎖衝突,死鎖產生。
我畫了一個圖方便你理解:
死鎖分析是比較複雜的,調試源碼能夠比較清晰的理清思路,上面是我調試源碼的一些結論,若是有理解有誤的地方,記得及時幫我指出。