2013-07-12 42 views
8

我有兩個非常簡單的表,我們稱它們爲[UserData1]和[UserData2]。他們都有[UserId]列作爲主鍵。我正在對這兩個表運行兩種類型的查詢。一個是針對特定用戶,回報率數據的SELECT語句:如何從連接表中選擇時避免死鎖?

SELECT <a subset of columns from both tables> 
FROM [UserData1] ud1 
    FULL OUTER JOIN [UserData2] ud2 ON ud1.[UserId] = ud2.[UserId] 
WHERE 
    ud1.[UserId] = @UserId OR ud2.[UserId] = @UserId 

另一種是爲特定用戶更新兩個表中的用戶數據的交易:

BEGIN TRANSACTION 

UPDATE [UserData1] 
SET <new values> 
WHERE [UserId] = @UserId 

UPDATE [UserData2] 
SET <new values> 
WHERE [UserId] = @UserId 

COMMIT TRANSACTION 

這裏的問題是在SELECT語句中獲取共享表鎖的順序未確定,如果SQL Server決定在[UserData1]之前鎖定[UserData2],則可能(並且實際上)會導致經典的死鎖情況。在這種情況下避免死鎖的最好方法是什麼?

將這些表合併到一張表中,對嗎?我希望這很容易。假設有一個理由讓他們分開。

READ UNCOMMITTED/NOLOCK提示?假設不能容忍髒讀。

SNAPSHOT隔離級別?這將解決問題,但我不確定涉及的開銷。

所以問題歸結爲:有沒有辦法保證連接表上的鎖的順序被獲取?

起初我以爲這可以通過FORCE ORDER查詢提示來實現,但後來我通過實驗發現它並不一定強制這些表被鎖定的順序。在這種特殊情況下的另一個解決方案是爲每個表分別執行SELECT查詢,然後在應用程序層中組合兩個單行記錄集,但如果我需要爲多個用戶進行查詢,我仍然傾向於全部獲取導致一個記錄集。

UPDATE:

這是死鎖跟蹤的摘錄:

Deadlock encountered .... Printing deadlock information 
    Wait-for graph 

    Node:1 
    KEY: 17:72057594039173120 (e21762ccf3dc) CleanCnt:3 Mode:X Flags: 0x1 
    Grant List 1: 
     Owner:0x00000020F75B0480 Mode: X  Flg:0x40 Ref:0 Life:02000000 SPID:72 ECID:0 XactLockInfo: 0x00000020EB13ED68 
     SPID: 72 ECID: 0 Statement Type: UPDATE Line #: 1 
     Input Buf: Language Event: (@UserId bigint,@DataColumn2 int)update 
    Requested by: 
    ResType:LockOwner Stype:'OR'Xdes:0x00000020FC98DA40 Mode: S SPID:75 BatchID:0 ECID:0 TaskProxy:(0x00000020DAB38608) Value:0xf75abbc0 Cost:(0/0) 

    Node:2 
    KEY: 17:72057594039107584 (e21762ccf3dc) CleanCnt:9 Mode:S Flags: 0x1 
    Grant List 1: 
     Owner:0x00000020EEBFE580 Mode: S  Flg:0x40 Ref:1 Life:00000000 SPID:75 ECID:0 XactLockInfo: 0x00000020FC98DA80 
     SPID: 75 ECID: 0 Statement Type: SELECT Line #: 1 
     Input Buf: Language Event: (@UserId bigint)select [t].[UserId], t.[DataColumn2], t1.[DataColumn1] 
    Requested by: 
    ResType:LockOwner Stype:'OR'Xdes:0x00000020EB13ED28 Mode: X SPID:72 BatchID:0 ECID:0 TaskProxy:(0x0000001F671C6608) Value:0xf75b5400 Cost:(0/456) 

    Victim Resource Owner: 
    ResType:LockOwner Stype:'OR'Xdes:0x00000020FC98DA40 Mode: S SPID:75 BatchID:0 ECID:0 TaskProxy:(0x00000020DAB38608) Value:0xf75abbc0 Cost:(0/0) 
    deadlock-list 
    deadlock victim=process20fda2ccf8 
    process-list 
    process id=process20fda2ccf8 taskpriority=0 logused=0 waitresource=KEY: 17:72057594039173120 (e21762ccf3dc) waittime=4526 ownerId=3416711 transactionname=SELECT lasttranstarted=2013-07-11T18:42:20.943 XDES=0x20fc98da40 lockMode=S schedulerid=20 kpid=2800 status=suspended spid=75 sbid=0 ecid=0 priority=0 trancount=0 lastbatchstarted=2013-07-11T18:42:20.950 lastbatchcompleted=2013-07-11T18:42:20.950 lastattention=1900-01-01T00:00:00.950 clientapp=.Net SqlClient Data Provider hostname=hostname hostpid=27716 loginname=loginname isolationlevel=read committed (2) xactid=3416711 currentdb=17 lockTimeout=4294967295 clientoption1=671088672 clientoption2=128056 
     executionStack 
     frame procname=adhoc line=1 stmtstart=36 sqlhandle=0x020000001fcbbe1423a0c65cc8411344c6040e879195af3a0000000000000000000000000000000000000000 
    select [t].[UserId], t.[DataColumn2], t1.[DataColumn1] from [UserData1] t1 full outer join [UserData2] t on t1.[UserId]=t.[UserId] where t.[UserId][email protected] or t1.[UserId][email protected] option (force order)  
     frame procname=unknown line=1 sqlhandle=0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 
    unknown  
     inputbuf 
    (@UserId bigint)select [t].[UserId], t.[DataColumn2], t1.[DataColumn1] from [UserData1] t1 full outer join [UserData2] t on t1.[UserId]=t.[UserId] where t.[UserId][email protected] or t1.[UserId][email protected] option (force order)  
    process id=process20fd055498 taskpriority=0 logused=456 waitresource=KEY: 17:72057594039107584 (e21762ccf3dc) waittime=4525 ownerId=3416764 transactionname=user_transaction lasttranstarted=2013-07-11T18:42:20.960 XDES=0x20eb13ed28 lockMode=X schedulerid=9 kpid=6024 status=suspended spid=72 sbid=0 ecid=0 priority=0 trancount=2 lastbatchstarted=2013-07-11T18:42:20.970 lastbatchcompleted=2013-07-11T18:42:20.970 lastattention=1900-01-01T00:00:00.970 clientapp=.Net SqlClient Data Provider hostname=hostname hostpid=27716 loginname=loginname isolationlevel=read committed (2) xactid=3416764 currentdb=17 lockTimeout=4294967295 clientoption1=671088672 clientoption2=128056 
     executionStack 
     frame procname=adhoc line=1 stmtstart=508 sqlhandle=0x02000000c0d74a32597ec460559a2d5dbdc92f7746cdce270000000000000000000000000000000000000000 
    update UserData2 set [LastModified]=getutcdate(), [DataColumn2]=[DataColumn2][email protected] where [UserId][email protected]  
     frame procname=unknown line=1 sqlhandle=0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 
    unknown  
     inputbuf 
    (@UserId bigint,@DataColumn2Increment int)update UserData2 set [LastModified]=getutcdate(), [DataColumn2]=[DataColumn2][email protected] where [UserId][email protected]  
    resource-list 
    keylock hobtid=72057594039173120 dbid=17 objectname=database_name.dbo.UserData1 indexname=1 id=lock20ec75b380 mode=X associatedObjectId=72057594039173120 
     owner-list 
     owner id=process20fd055498 mode=X 
     waiter-list 
     waiter id=process20fda2ccf8 mode=S requestType=wait 
    keylock hobtid=72057594039107584 dbid=17 objectname=database_name.dbo.UserData2 indexname=1 id=lock20ec07f600 mode=S associatedObjectId=72057594039107584 
     owner-list 
     owner id=process20fda2ccf8 mode=S 
     waiter-list 
     waiter id=process20fd055498 mode=X requestType=wait 

顯然運行SELECT語句的過程中獲得的[UserData2]之前[UserData1]表上的鎖,儘管FORCE ORDER提示。

回答

0

第一件事(我認爲)是第一個查詢中的where子句是多餘的。在Join中你有同樣的事情,因爲你正在做一個完整的外連接,所以在連接中更好。

在避免死鎖方面,它可能與第一個查詢在刪除讀鎖方面的處理方式有關。如果應用程序正在讀取數據並且這不是用戶事務的一部分,那麼一旦讀取完成,第二個查詢將能夠完成,並且不會發生死鎖。

你是否在你的環境中陷入僵局,或者只是猜測你會遇到死鎖。如果你發佈了死鎖圖表,那麼我們可以看到實際的鎖是什麼。

+0

是的,我正在陷入僵局。我用死鎖跟蹤更新了這個問題。它看起來像運行SELECT查詢的進程持有第一個鎖([UserData2]),同時嘗試獲取[UserData1]上的另一個鎖,而不是丟棄第一個鎖。 –

1

隨着READ COMMITTED選擇不應該參加僵局,因爲它應該只有一次獲得一個鎖。讀取鎖定的行後,鎖可以立即釋放。

我特別推薦你打開快照隔離。它將解決問題。熟悉所涉及的3個開銷:增加的行大小,tempdb寫入和微小的讀取開銷。大多數時候他們沒有意義。

+0

同意快照隔離將解決問題,並且開銷可能可以忽略不計。我只是好奇,如果這可以通過其他方式解決。 例如,是否可以安排連接表一次鎖定一個表?從死鎖蹤跡(添加到問題主體)看來,在SELECT試圖鎖定另一個表時,不會釋放一個表上的鎖([UserData2])。 –

+0

X鎖永遠不會釋放,以確保回滾工作。我更困惑爲什麼選擇同時需要多個鎖? SQL Server通常鎖定行,而不是表。你應該調試這個。是否有其他查詢是選擇交易的一部分?添加'SET TRANSACTION ISOLATION LEVEL READ COMMITTED'。你可以通過'TABLOCKX'來修復這個更新中的表,但這對於併發來說是很糟糕的。也許您還需要在選擇查詢中以相同的順序對它們進行X鎖定。 – usr