2013-07-12 4 views
8

私は2つの非常に単純なテーブルを持っています。それらを[UserData1]と[UserData2]と呼んでみましょう。どちらも[UserId]列を主キーとして持っています。私はこれら2つのテーブルに対して2種類のクエリを実行しています。一つは、特定のユーザーのために合成されたデータを返す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 

ここで問題がありますSQL Serverが[UserData1]の前に[UserData2]をロックすることを決定した場合、古典的なデッドロック状態に陥る可能性があります(実際にはそうです)。この場合、デッドロックを回避する最良の方法は何でしょうか?

これらの表を1つの表にマージします。私はそれが簡単だったと思います。それらを分離しておく理由があるとします。

READ UNCOMMITTED/NOLOCKヒント?汚れた読み込みが許容できないとします。

SNAPSHOT分離レベル?これで問題は解決しますが、オーバーヘッドについてはわかりません。

したがって、質問は次のようになります。は、結合されたテーブルのロックを取得する順序を保証する手段ですか?

最初はこれがFORCE ORDERクエリヒントで実現できると思っていましたが、テーブルがロックされている順序を必ずしも強制するわけではありません。この特定の場合の別の解決方法は、各テーブルに対して別々のSELECTクエリを発行し、アプリケーションレイヤで2つの1行レコードセットを結合することですが、複数のユーザーに対してクエリを作成する必要がある場合は、 1つのレコードセットになります。

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文を実行しているプロセスは、FORCE ORDERヒントにもかかわらず、[UserData1]の前に[UserData2]テーブルのロックを取得しました。

答えて

0

最初のことは、最初のクエリのwhere句が冗長であることです。あなたはJoinで同じことをしており、あなたは完全な外部joinを行っているので、joinでより良いです。

デッドロックを回避する観点からは、読み取りロックを解除するという点で最初のクエリがどのように処理されるかが関係していると考えられます。アプリケーションがデータを読み取っているだけで、これがユーザートランザクションの一部でない場合は、読み取りが完了すると2番目のクエリが完了し、デッドロックが発生しません。

環境内でデッドロックが発生しているか、デッドロックが発生していると推測されますか。デッドロックグラフを投稿すると、実際のロックが何であるかを見ることができます。

+0

はい、デッドロックが発生しています。私はデッドロックトレースで質問を更新しました。最初のロックを破棄するのではなく、[UserData1]でもう一方のロックを取得しようとしているときに、SELECTクエリを実行しているプロセスが最初のロック([UserData2])に保持されているようです。 –

1

READ COMMITTEDでは、一度に1つのロックしか取得できないため、selectはデッドロックに参加すべきではありません。ロックは、ロックされた行を読み取った直後に解放することができます。

スナップショットアイソレーションをオンにすることをお勧めします。問題を解決します。行のサイズの増加、tempdbの書き込み、小さな読み取りのオーバーヘッドの3つのオーバーヘッドに慣れてください。ほとんどの場合、意味がありません。

+0

スナップショットの分離が問題を解決し、おそらくオーバーヘッドは無視できると考えています。これが他の手段で解決できるのであれば私はちょうど興味があります。 たとえば、結合されたテーブルを1つずつロックするように設定できますか?デッドロックトレース(質問本体に追加)から、SELECTが他のテーブルをロックしようとしている間に、あるテーブル([UserData2])のロックが解除されないように見えます。 –

+0

Xロックは決して解放されないので、ロールバックが保証されます。なぜ私はselectが複数のロックを同時に取るのだろうと困惑していますか? SQL Serverは通常、テーブルではなく行をロックします。これをデバッグする必要があります。選択トランザクションの他のクエリがありますか? 'SET TRANSACTION ISOLATION LEVEL READ COMMITTED'を追加してください。更新のテーブルを 'TABLOCKX'することでこれを修正できますが、これは並行処理にとって恐ろしいことです。おそらく、あなたはまた、選択したクエリで同じ順序でそれらをXロックする必要があります。 – usr