【1】死鎖發生及基本信息
死鎖問題,想不明白為什么會死鎖,求大佬分析詳細原因和加鎖、等待之類的詳細過程過程,以便理解 解決
信息如下:
【1.1】被死鎖的基本信息
tOnlineUser 死鎖發生表的索引信息:
名稱:IX_tOnlineUser
類型:nonclustered, ignore duplicate keys, unique located on PRIMARY
索引列:iUserID
【1.2】死鎖圖片與死鎖代碼信息
--SP1 被犧牲(圖中左邊)
SELECT @iDbGsID=iGameServerID,@bDbState=bState FROM tOnlineUser WHERE iUserID = @iUserID
UPDATE tOnlineUser SET bState=3 WHERE iUserID = @iUserID
--SP2 (圖中右邊)
DELETE FROM tOnlineUser WHERE iUserID=@iUserID
INSERT tOnlineUser (iUserID,iGameServerID,bState) VALUES (@iUserID,@iGsID,@bOnlineState)
【1.3】死鎖XML信息
Deadlock graph <deadlock-list> <deadlock victim="process47704d8"> <process-list> <process id="processff12e8" taskpriority="0" logused="84" waitresource="KEY: 14:72057594038976512 (9e00c0f50f63)" waittime="3187" ownerId="862602370" transactionname="DELETE" lasttranstarted="2020-09-24T20:46:44.200" XDES="0xffffffff80048380" lockMode="X" schedulerid="7" kpid="1236" status="suspended" spid="71" sbid="0" ecid="0" priority="0" transcount="2" lastbatchstarted="2020-09-24T20:46:44.200" lastbatchcompleted="2020-09-24T20:46:44.200" hostname="BOX-5" hostpid="1932" loginname="BOX_User" isolationlevel="read committed (2)" xactid="862602370" currentdb="14" lockTimeout="4294967295" clientoption1="673185824" clientoption2="128056"> <executionStack> <frame procname="BOX_RunCenter.dbo.me_UserOnlineState" line="35" stmtstart="890" stmtend="1014" sqlhandle="0x03000e005cbf2019d1d51601ada700000100000000000000"> DELETE FROM tOnlineUser WHERE iUserID=@iUserID --在線 或 掉線 </frame> </executionStack> <inputbuf> Proc [Database Id = 14 Object Id = 421576540] </inputbuf> </process> <process id="process47704d8" taskpriority="0" logused="0" waitresource="RID: 14:1:185:196" waittime="3187" ownerId="862602372" transactionname="SELECT" lasttranstarted="2020-09-24T20:46:44.200" XDES="0xffffffff9f80a7b0" lockMode="S" schedulerid="15" kpid="8704" status="suspended" spid="129" sbid="0" ecid="0" priority="0" transcount="0" lastbatchstarted="2020-09-24T20:46:44.200" lastbatchcompleted="2020-09-24T20:46:44.200" hostname="iZjcdsetuetu8jZ" hostpid="692" loginname="BOX_User" isolationlevel="read committed (2)" xactid="862602372" currentdb="14" lockTimeout="4294967295" clientoption1="673185824" clientoption2="128056"> <executionStack> <frame procname="BOX_RunCenter.dbo.me_GetUserOnlineServerID" line="39" stmtstart="1206" stmtend="1404" sqlhandle="0x03000e00b1524416c8d51601ada700000100000000000000"> SELECT @iGsID = iGameServerID,@bState=bState FROM tOnlineUser WHERE iUserID = @iUserID --沒有記錄 </frame> </executionStack> <inputbuf> Proc [Database Id = 14 Object Id = 373576369] </inputbuf> </process> </process-list> <resource-list> <keylock hobtid="72057594038976512" dbid="14" objectname="BOX_RunCenter.dbo.tOnlineUser" indexname="IX_tOnlineUser" id="lockffffffffd7158ac0" mode="U" associatedObjectId="72057594038976512"> <owner-list> <owner id="process47704d8" mode="S"/> </owner-list> <waiter-list> <waiter id="processff12e8" mode="X" requestType="convert"/> </waiter-list> </keylock> <ridlock fileid="1" pageid="185" dbid="14" objectname="BOX_RunCenter.dbo.tOnlineUser" id="lockfffffffffd721240" mode="X" associatedObjectId="72057594038910976"> <owner-list> <owner id="processff12e8" mode="X"/> </owner-list> <waiter-list> <waiter id="process47704d8" mode="S" requestType="wait"/> </waiter-list> </ridlock> </resource-list> </deadlock> </deadlock-list>
【2】分析
【2.1】加鎖過程分析
(1)看你的死鎖示意圖,SP1應該是在執行update,update會在涉及到的鍵列上先放置U鎖,然后通過where條件是定位行。
(2)但是這個時候SP2的delete已經在定位到的行上放置了X鎖,然后就死鎖了
(3)為什么SP2 還需要鍵鎖的X呢?因為已經確定刪除行了,那么對應的索引鍵值也要刪除掉。
總結就是:
我們都知道update和delete 等操作,是需要先查詢,然后再進行操作的,那么總結核心過程應該如下:
(1)SP1的 update和 sp2 delete同時運行針對同一個索引IUserID 值,先同時獲取到索引的S鎖用來查詢
(2)然后SP2的 delete 先一步獲取到了對應行的 RID鎖(行鎖),它操作刪除行。而此時SP1在等待SP2釋放該索引鍵值IUserID對應的 RID頁鎖。
(3)這個時候SP2 的 delete 刪除完行數據后,想要獲取 IUserID 的鍵鎖,因為是要把對應的索引鍵值一起刪掉。但這個時候,索引鍵值的 S鎖 還持有在 SP1的 update上。
(4)最終, SP1的 update 擁有IUserID 的索引鍵值鎖(S),SP2的 delete 有用 IUserID 所對應行的 RID行鎖(X),他們互相需要對方的資源鎖,然后又互相等待,形成了死鎖的循環等待。
【3】解決辦法
(1)UPDLOCK
最終使用了這種辦法解決
指定采用更新鎖並保持到事務完成。 UPDLOCK 僅對行級別或頁級別的讀操作采用更新鎖。
如果將 UPDLOCK 與 TABLOCK 組合使用或出於一些其他原因采用表級鎖,將采用排他 (X) 鎖。
SELECT @iDbGsID=iGameServerID,@bDbState=bState
FROM tOnlineUser with(updlock) WHERE iUserID = @iUserID
(2)消除額外的鍵查找鎖需的鎖
直接在 iUserID 上加上 聚集索引,不過要是大表 代價太大了,很影響性能,不太可取
(3)讀操作時取消獲取鎖
使用 with nolock 、或者切換快照、讀已提交快照隔離級別解決。
【參考文檔】
【0】必看參考:SQL SERVER - 談死鎖的監控分析解決思路:https://www.cnblogs.com/xinysu/p/6511360.html
【1】select 與 update 的死鎖:https://blog.csdn.net/ajianchina/article/details/46807131
【2】高並發select 與 update引起的死鎖:https://blog.csdn.net/weixin_44774463/article/details/108204456