為什麼我在 SQL 中遇到 1 個讀取和 1 個寫入執行緒的死鎖
如果有人可以請幫助我理解為什麼以下交易死鎖?我為每個表提供 2 個事務和索引:
交易一:
SELECT blockId, blockData, syncedToGeneration, itemId FROM blocks WHERE indexId=@indexId and itemId IN (SELECT itemId FROM KeywordReferences WHERE indexId=@indexId AND keywordRootId IN (360,4498,359,1229))
交易2:
UPDATE blocks SET blockData=@blockData, syncedToGeneration=@syncedToGeneration WHERE indexId=@indexId AND blockId=@blockId
(請注意,第一個事務中的“IN”部分要長得多,包含大約 30 個值,為了便於閱讀,我將其截斷)
blocks 表有以下索引:
indexId->blockId (Clustered)
indexId->itemId
indexId->itemId
keywordReferences 表具有以下索引:
indexId_>keywordRootId (Clustered)
indexId->keywordRootId->score
indexId->itemId
indexId->blockId
波紋管是死鎖圖xml輸出:
<deadlock-list> <deadlock victim="process5a274c8"> <process-list> <process id="process5a274c8" taskpriority="0" logused="0" waitresource="PAGE: 5:1:91671" waittime="2709" ownerId="122348" transactionname="SELECT" lasttranstarted="2012-04-19T21:23:26.680" XDES="0xf382aca0" lockMode="S" schedulerid="4" kpid="10992" status="suspended" spid="69" sbid="0" ecid="0" priority="0" trancount="0" lastbatchstarted="2012-04-19T21:23:26.650" lastbatchcompleted="2012-04-19T21:23:26.647" clientapp=".Net SqlClient Data Provider" hostname="AMIT-PC" hostpid="6752" loginname="sa" isolationlevel="read committed (2)" xactid="122348" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056"> <executionStack> <frame procname="adhoc" line="1" stmtstart="34" sqlhandle="0x020000002e9a3633b816ffc89dc234b4c0351887e4e1b2cf"> SELECT blockId, blockData, syncedToGeneration, itemId FROM blocks WHERE indexId=@indexId and itemId IN (SELECT itemId FROM KeywordReferences WHERE indexId=@indexId AND keywordRootId IN (360,4498,359,1229,2143,14330,7661,3755,1156,21490,5567,1933,429,28197,2,3165,524,3182,2655,27262,17407,2673,570,1478,3802,6838,19668,17,6586,2484,2794,1640,5171,2558,6592,5833,695,1199,2307,335,1351,6651,6899,3740,7048,22030,14356,597,3175,3965,3297,2711,14484,2761,2265,28,1647,3223,226,304,298,1157,197,2696,21172,19149,9,1159,135,1,3166,23325)) </frame> <frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000"> unknown </frame> </executionStack> <inputbuf> (@indexId bigint)SELECT blockId, blockData, syncedToGeneration, itemId FROM blocks WHERE indexId=@indexId and itemId IN (SELECT itemId FROM KeywordReferences WHERE indexId=@indexId AND keywordRootId IN (360,4498,359,1229,2143,14330,7661,3755,1156,21490,5567,1933,429,28197,2,3165,524,3182,2655,27262,17407,2673,570,1478,3802,6838,19668,17,6586,2484,2794,1640,5171,2558,6592,5833,695,1199,2307,335,1351,6651,6899,3740,7048,22030,14356,597,3175,3965,3297,2711,14484,2761,2265,28,1647,3223,226,304,298,1157,197,2696,21172,19149,9,1159,135,1,3166,23325)) </inputbuf> </process> <process id="process5a13b88" taskpriority="0" logused="215304" waitresource="PAGE: 5:1:91669" waittime="2910" ownerId="128212" transactionname="user_transaction" lasttranstarted="2012-04-19T21:23:28.567" XDES="0xedcd9000" lockMode="IX" schedulerid="2" kpid="5500" status="suspended" spid="68" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2012-04-19T21:23:29.007" lastbatchcompleted="2012-04-19T21:23:29.007" clientapp=".Net SqlClient Data Provider" hostname="AMIT-PC" hostpid="6752" loginname="sa" isolationlevel="read committed (2)" xactid="128212" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056"> <executionStack> <frame procname="adhoc" line="1" stmtstart="154" sqlhandle="0x02000000f4d83b0df2bfedc5a346288c21fa78e07eb152f6"> UPDATE blocks SET blockData=@blockData, syncedToGeneration=@syncedToGeneration WHERE indexId=@indexId AND blockId=@blockId </frame> <frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000"> unknown </frame> </executionStack> <inputbuf> (@indexId bigint,@blockId int,@blockData ntext,@syncedToGeneration int)UPDATE blocks SET blockData=@blockData, syncedToGeneration=@syncedToGeneration WHERE indexId=@indexId AND blockId=@blockId </inputbuf> </process> </process-list> <resource-list> <pagelock fileid="1" pageid="91671" dbid="5" objectname="isqdb.dbo.blocks" id="lock5c54700" mode="IX" associatedObjectId="72057594043826176"> <owner-list> <owner id="process5a13b88" mode="IX"/> </owner-list> <waiter-list> <waiter id="process5a274c8" mode="S" requestType="wait"/> </waiter-list> </pagelock> <pagelock fileid="1" pageid="91669" dbid="5" objectname="isqdb.dbo.blocks" id="lock5b84780" mode="S" associatedObjectId="72057594043826176"> <owner-list> <owner id="process5a274c8" mode="S"/> </owner-list> <waiter-list> <waiter id="process5a13b88" mode="IX" requestType="wait"/> </waiter-list> </pagelock> </resource-list> </deadlock> </deadlock-list>
這看起來像一個典型的表掃描案例(可能缺少索引,但可能更多)。SELECT 選擇了頁鎖粒度,表示大掃描(在
blocks
表上)。還要注意所有鎖是如何在同一個資源行集、聚集索引上的,這是 SELECT 不使用選擇性二級索引來定位行的另一個指示。您似乎希望 上的非聚集索引(indexId, itemId)
(您描述為現有的)會被選擇,並且如果選擇它,查詢可能不會死鎖。從目前的情況來看,它看起來很可能因為選擇性而被忽略(它達到了臨界點)。在不了解您的工作負載和數據模型的情況下,很難建議更好的索引。至少我可以說項目的要求blockData, syncedToGeneration
在 SELECT 列表中使索引不被覆蓋,因此也許將它們添加為包含的列將是第一步。但是,如果不知道這些列的大小(blockData
似乎是一個大列的名稱……),再次難以預測結果。為了舉例說明為什麼掃描是罪魁禍首,請考慮由 UPDATE 語句更新的任意兩行。UPDATE 將按順序更新它們,
(indexId, blockId)
但 SELECT 掃描將按順序看到它們(indexId, itemId)
。除非在 和 之間存在函式依賴關係itemId
,blockId
以保證兩個索引鍵以相同的順序返回所有行,否則在聚集索引和非聚集索引之間總會有一些順序顛倒的行。它的要點是,因為 SELECT 進行掃描,所以保證訪問每一行。因此,任何由 UPDATE 更新的行都會被掃描訪問。有四種可能:
- UPDATEd 的兩行都落後於(按聚集索引鍵順序)掃描的目前位置(正在掃描的目前行)。這是安全的,UPDATE 阻塞並等待掃描。
- UPDATEd 的兩行都在掃描位置之前。同樣安全的是,掃描會阻止並等待更新。
- UPDATEd 的第一行在目前掃描位置的後面,但第二行在前面。安全,更新塊。
- 第一行 UPDATEd 在掃描的目前位置之前,但第二行在後面。這是一個有保證的死鎖。請注意,行在聚集索引順序中越多,一個在前面和一個在任何掃描位置後面的機率就越高。
在存在掃描的情況下,多行更新總是會出現這種情況。解決方案通常是用更具選擇性的操作代替掃描,這具有提高性能的額外好處。如果 SELECT 和 UPDATE 只訪問他們真正希望處理的行,那麼只有在同一邏輯數據上同時發生兩個操作時才會發生死鎖(即同時讀取和更新同一項目),但大多數時候在 OLTP 系統上,這是由業務工作流阻止的。它仍然可能發生,但頻率大大降低。附帶說明一下,即使對不相交的邏輯項進行操作,由於雜湊衝突,它仍然是機率遊戲。
實際上,一種簡單的方法是部署快照隔離。但是潛在的問題(掃描)只會被掩蓋,不會得到緩解。掃描引起的其他問題(性能差、延遲高和響應時間慢、緩衝池污染等)仍然存在。您應該修復掃描問題並部署快照隔離。
PS 請注意,掃描使用 PAGE 粒度鎖的事實沒有相關性。如果掃描使用 ROW 粒度,死鎖只會發生在行而不是頁上。但是選擇 PAGE 粒度這一事實對於掃描來說意義重大。
看起來這是一個可能的解釋:
- select 在 itemId 上的非聚集索引上保持一個共享鎖,等待在聚集索引上獲取另一個共享鎖,以便它可以檢索更多列。
- 更新修改了聚集索引的一頁,當然保留了排他鎖。它也等待修改非聚集索引。
您可以發布相關表和索引的 DDL 嗎?