大招落地:MySQL 插入更新死鎖源碼分析

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"今天再來分析一個死鎖場景。下面開始真正的內容。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"建表語句:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"CREATE TABLE `tenant_config` (\n `id` bigint(21) NOT NULL AUTO_INCREMENT,\n `tenant_id` int(11) NOT NULL,\n `open_card_point` int(11) DEFAULT NULL,\n PRIMARY KEY (`id`),\n UNIQUE KEY `uidx_tenant` (`tenant_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"表中有一條初始化數據:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"INSERT INTO `tenant_config` (`tenant_id`, `open_card_point`) VALUES (123,0);\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"數據庫隔離級別:RC"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"兩條 insert,兩條 update"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事務 1 和事務 2 語句一毛一樣,都是下面這樣:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"INSERT INTO `tenant_config` ( `tenant_id`, `open_card_point`) VALUES (123,111111);\n\nUPDATE tenant_config SET open_card_point = 0 where tenant_id = 123;\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"代碼的邏輯大概如下,先插入,如果有衝突則更新"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"try {\n insert();\n} catch (DuplicateKeyException e) {\n update()\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"死鎖條件的過程如下"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事務 1:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"INSERT INTO `tenant_config` ( `tenant_id`, `open_card_point`) VALUES (123,111111);\nERROR 1062 (23000): Duplicate entry '123' for key 'uidx_tenant'\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"加鎖情況,對 uk 加 S 鎖,如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/45/4507fbe8230828bbf08b7fd0ebd5130b.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事務 2:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"INSERT INTO `tenant_config` ( `tenant_id`, `open_card_point`) VALUES (123,111111);\nERROR 1062 (23000): Duplicate entry '123' for key 'uidx_tenant'\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"加鎖情況,對 uk 加 S 鎖,如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d5/d5bc7b18fb97c054edfcad4ad75fb90a.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事務 1:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"UPDATE tenant_config SET open_card_point = 0 where tenant_id = 123;\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對 uk 加 X 鎖,因爲事務 2 獲取了 S 鎖,進入鎖等待"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d5/d537a8e46527c5518f721d7727f3863c.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事務 2:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"UPDATE tenant_config SET open_card_point = 0 where tenant_id = 123;\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同樣想對 uk 加 X 鎖,死鎖條件產生:事務 2 拿到了 S 鎖,想加 X 鎖,事務 1 拿到了 S 鎖,也想加 X 鎖,彼此都在等對方的 S 鎖。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/bf/bf9c48e48e82145696591ddfcab6b6c9.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種情況是最簡單的,如果只是這麼簡單,我就不會寫了,哈哈,下面來看第二種情況。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"一條 insert,兩條 update"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一步:事務 1,插入唯一鍵衝突"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"begin;\nINSERT INTO `tenant_config` ( `tenant_id`, `open_card_point`) VALUES (123,111111);\n\nERROR 1062 (23000): Duplicate entry '123' for key 'uidx_tenant'\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二步:事務 2"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"begin;\nUPDATE tenant_config SET open_card_point = 0 where tenant_id = 123 and 1 =1;\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第三步:事務 1"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"UPDATE tenant_config SET open_card_point = 0 where tenant_id = 123 and 1 =1;\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"出現:事務 2 死鎖"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"分析過程如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事務 1"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"INSERT INTO `tenant_config` ( `tenant_id`, `open_card_point`) VALUES (123,111111);\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對 uk 加 S 鎖,這個沒有什麼歧義。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/45/4507fbe8230828bbf08b7fd0ebd5130b.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來事務 2"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"UPDATE tenant_config SET open_card_point = 0 where tenant_id = 123 and 1 =1;\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個對 ux 加 X 鎖,進入鎖等待狀態,這個也沒有什麼問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d0/d09971a81e47ab828a58586be95e58dc.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來,事務 1 執行 update,情況就複雜很多了,也是想獲取 X 鎖,但是沒有那麼順利。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"進入死鎖檢測流程,重點代碼在lock_deadlock_occurs()函數,最近會進入 lock_deadlock_recursive()遞歸調用函數。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/84/84efd6f0604284030474ec5f0b5c8822.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"start 表示頂層調用該函數的事務指針,比如現在正在執行的事務 1 就是 start"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"wait_lock 表示想要獲取的鎖,這裏是事務 1 對 uk 的 X 鎖。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"trx 等待鎖的事務指針"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"死鎖的本質是:在遞歸過程中,如果衝突出現的鎖事務id等於頂層事務id(lock_trx == start),則說明有環,就發生死鎖。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8d/8d71b1b58da7ad95265cdba255d8f1f0.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以下記事務 1 爲 t1,事務 2 爲 t2"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"第一次遞歸"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"wait_lock 屬於 t1 的 lock_X,就是 t1 update 想獲取的 X 鎖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1a/1a961233fd7ae766d953744f1a5dad6d.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個時候會檢查記錄上所有的鎖,第一個鎖是 t1 事務的 S 鎖,第二個鎖是 t2 事務等待狀態的 X 鎖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"檢查第一把鎖,t1 事務的 S 鎖,因爲與 wait_lock 屬於同一個事務,沒有衝突,繼續檢查第二把鎖。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/11/11d1957e4211580e1829a57ebe69fa06.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"檢查第二把鎖,是 t2 事務處於等待狀態的 X 鎖,是互斥的,而且 t2 的 X 鎖是處於等待狀態的,開始第二次遞歸調用,檢查 t2 的 X 鎖,查看它在等待什麼鎖。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f9/f914110c5f0f3872a932a0dbdfb9bd35.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"第二次遞歸"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此時傳入的 start 沒變,wait_lock 變爲了 t2 的 X 鎖,也就是把 t2 的 X 鎖拿出來檢測,看跟現有鎖有哪些依賴。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"t2 的 X 鎖在等待 t1 的 S 鎖,lock_trx 等於 start,成環死鎖產生。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/62/629a76aab1f098c7df9568e36e6e616d.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"也就是:t1 的 insert 插入加了 S 鎖,t2 的 X 鎖雖然沒加成功,但是真實存在,標記爲等待狀態。t1 再想獲取 X 鎖,發現與 t2 等待狀態的 X 鎖衝突。再次檢測,發現 t2 等待狀態的 X 鎖與 t1 的 S 鎖衝突,死鎖產生。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我畫了一個圖方便你理解:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/cf/cf1f01b6a2df82a86e59a67bc50aebfe.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"後記"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"死鎖分析是比較複雜的,調試源碼可以比較清晰的理清思路,上面是我調試源碼的一些結論,如果有理解有誤的地方,記得及時幫我指出。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"看完三件事❤️"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":""}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"點贊,轉發,有你們的 『點贊和評論』,纔是我創造的動力。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"關注公衆號 『 "},{"type":"text","marks":[{"type":"strong"}],"text":"java爛豬皮"},{"type":"text","text":" 』,不定期分享原創知識。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"同時可以期待後續文章ing🚀"}]}]}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/34/34172ad7f3cc8e0f28bd1fc6ca2d2b68.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作者:挖坑的張師傅"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"出處:https://club.perfma.com/article/1982680"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章