非クラスター化インデックスで異なる行を更新するときのデッドロック


13

idフィールドでクラスター化インデックスと非クラスター化インデックスを使用すると、ロックの動作が異なることに気付きながら、デッドロックの問題を解決しています。clustedインデックスまたはプライマリキーがidフィールドに適用されると、デッドロックの問題は解決されるようです。

異なる行に対して1つ以上の更新を実行する異なるトランザクションがあります。たとえば、トランザクションAはID = aの行のみを更新し、tx BはID = bの行のみを更新します。

そして、インデックスなしでは、更新はすべての行の更新ロックを取得し、必要に応じて排他ロックに変換し、最終的にデッドロックにつながることを理解しています。しかし、非クラスター化インデックスでは、デッドロックが依然として存在する理由を見つけることができません(ただし、ヒット率は低下しているようです)

データ表:

CREATE TABLE [dbo].[user](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [userName] [nvarchar](255) NULL,
    [name] [nvarchar](255) NULL,
    [phone] [nvarchar](255) NULL,
    [password] [nvarchar](255) NULL,
    [ip] [nvarchar](30) NULL,
    [email] [nvarchar](255) NULL,
    [pubDate] [datetime] NULL,
    [todoOrder] [text] NULL
)

デッドロックトレース

deadlock-list
deadlock victim=process4152ca8
process-list
process id=process4152ca8 taskpriority=0 logused=0 waitresource=RID: 5:1:388:29 waittime=3308 ownerId=252354 transactionname=user_transaction lasttranstarted=2014-04-11T00:15:30.947 XDES=0xb0bf180 lockMode=U schedulerid=3 kpid=11392 status=suspended spid=57 sbid=0 ecid=0 priority=0 trancount=2 lastbatchstarted=2014-04-11T00:15:30.953 lastbatchcompleted=2014-04-11T00:15:30.950 lastattention=1900-01-01T00:00:00.950 clientapp=.Net SqlClient Data Provider hostname=BOOD-PC hostpid=9272 loginname=getodo_sql isolationlevel=read committed (2) xactid=252354 currentdb=5 lockTimeout=4294967295 clientoption1=671088672 clientoption2=128056
executionStack
frame procname=adhoc line=1 stmtstart=62 sqlhandle=0x0200000062f45209ccf17a0e76c2389eb409d7d970b0f89e00000000000000000000000000000000
update [user] WITH (ROWLOCK) set [todoOrder]=@para0 where id=@owner
frame procname=unknown line=1 sqlhandle=0x00000000000000000000000000000000000000000000000000000000000000000000000000000000
unknown
inputbuf
(@para0 nvarchar(2)<c/>@owner int)update [user] WITH (ROWLOCK) set [todoOrder]=@para0 where id=@owner
process id=process4153468 taskpriority=0 logused=4652 waitresource=KEY: 5:72057594042187776 (3fc56173665b) waittime=3303 ownerId=252344 transactionname=user_transaction lasttranstarted=2014-04-11T00:15:30.920 XDES=0x4184b78 lockMode=U schedulerid=3 kpid=7272 status=suspended spid=58 sbid=0 ecid=0 priority=0 trancount=2 lastbatchstarted=2014-04-11T00:15:30.960 lastbatchcompleted=2014-04-11T00:15:30.960 lastattention=1900-01-01T00:00:00.960 clientapp=.Net SqlClient Data Provider hostname=BOOD-PC hostpid=9272 loginname=getodo_sql isolationlevel=read committed (2) xactid=252344 currentdb=5 lockTimeout=4294967295 clientoption1=671088672 clientoption2=128056
executionStack
frame procname=adhoc line=1 stmtstart=60 sqlhandle=0x02000000d4616f250747930a4cd34716b610a8113cb92fbc00000000000000000000000000000000
update [user] WITH (ROWLOCK) set [todoOrder]=@para0 where id=@uid
frame procname=unknown line=1 sqlhandle=0x00000000000000000000000000000000000000000000000000000000000000000000000000000000
unknown
inputbuf
(@para0 nvarchar(61)<c/>@uid int)update [user] WITH (ROWLOCK) set [todoOrder]=@para0 where id=@uid
resource-list
ridlock fileid=1 pageid=388 dbid=5 objectname=SQL2012_707688_webows.dbo.user id=lock3f7af780 mode=X associatedObjectId=72057594042122240
owner-list
owner id=process4153468 mode=X
waiter-list
waiter id=process4152ca8 mode=U requestType=wait
keylock hobtid=72057594042187776 dbid=5 objectname=SQL2012_707688_webows.dbo.user indexname=10 id=lock3f7ad700 mode=U associatedObjectId=72057594042187776
owner-list
owner id=process4152ca8 mode=U
waiter-list
waiter id=process4153468 mode=U requestType=wait

また、興味深い可能性のある関連する発見は、クラスター化インデックスと非クラスター化インデックスが異なるロック動作を持っているように見えることです。

クラスター化インデックスを使用する場合、キーの排他ロックと、更新時にRIDの排他ロックがあります。非クラスター化インデックスが使用されている場合、2つの異なるRIDに2つの排他ロックがあり、混乱を招きます。

誰かがこれについて理由を説明できれば助かります。

テストSQL:

use SQL2012_707688_webows;
begin transaction;
update [user] with (rowlock) set todoOrder='{1}' where id = 63501
exec sp_lock;
commit;

クラスター化インデックスとしてidを使用:

spid    dbid    ObjId   IndId   Type    Resource    Mode    Status
53  5   917578307   1   KEY (b1a92fe5eed4)                      X   GRANT
53  5   917578307   1   PAG 1:879                               IX  GRANT
53  5   917578307   1   PAG 1:1928                              IX  GRANT
53  5   917578307   1   RID 1:879:7                             X   GRANT

非クラスター化インデックスとしてidを使用

spid    dbid    ObjId   IndId   Type    Resource    Mode    Status
53  5   917578307   0   PAG 1:879                               IX  GRANT
53  5   917578307   0   PAG 1:1928                              IX  GRANT
53  5   917578307   0   RID 1:879:7                             X   GRANT
53  5   917578307   0   RID 1:1928:18                           X   GRANT

EDIT1:インデックスなしのデッドロックの詳細
2つのtx AとBがあり、それぞれに2つの更新ステートメントがあり、異なる行
tx Aがあるとします

update [user] with (rowlock) set todoOrder='{1}' where id = 63501
update [user] with (rowlock) set todoOrder='{2}' where id = 63501

tx B

update [user] with (rowlock) set todoOrder='{3}' where id = 63502
update [user] with (rowlock) set todoOrder='{4}' where id = 63502

{1}と{4}はデッドロックの可能性があります。

{1}で、テーブルスキャンを実行する必要があるため、行63502に対してUロックが要求されます。条件に一致するため、行63501でXロックが保持されている可能性があります。

{4}で、行63501に対してUロックが要求され、63502に対してXロックがすでに保持されています

txAは63501を保持し、63502を待機しますが、txBは63502を保持して63501を待機します。これはデッドロックです

EDIT2:ここで私のテストケース のバグは違いのある状況になっていることがわかりますが、混乱し申し訳ありませんが、バグは違いのある状況を作り、最終的にデッドロックを引き起こすようです。

ポールの分析がこのケースで私を本当に助けたので、私はそれを答えとして受け入れます。

私のテストケースのバグにより、2つのトランザクションtxAとtxBは、次のように同じ行を更新できます。

tx A

update [user] with (rowlock) set todoOrder='{1}' where id = 63501
update [user] with (rowlock) set todoOrder='{2}' where id = 63501

tx B

update [user] with (rowlock) set todoOrder='{3}' where id = 63501

{2}および{3}は、次の場合にデッドロックの可能性があります。

txAは、RIDのXロックを保持しながらキーのUロックを要求します({1}の更新のため)txBは、キーのUロックを保持しながらRIDのUロックを要求します


1
トランザクションが同じ行を2回更新する必要がある理由がわかりません。
ypercubeᵀᴹ

@ypercube良い点、それは私が改善すべきことです。しかし、この場合、私はちょうどロックの行動をより深く理解したい
Bood

例えば異なる列可能性があり、私はそれは複雑なロジックニーズを持つアプリケーションが同じテキサス州で二度同じ行を更新することは可能だと思うより多くの思考の後@ypercube
Bood

回答:


16

...なぜクラスタ化インデックスを使用しても、デッドロックはまだ存在しています(ただし、ヒット率は低下しているようです)

質問は正確には明確ではありません(たとえばid、各トランザクションに更新の数と値が含まれる)が、[id]値が重複し、IDが1つのトランザクション内で複数の単一行の更新を行うと、明らかなデッドロックシナリオが1つ発生します別の[id]順序で更新:

[T1]: Update id 2; Update id 1;
[T2]: Update id 1; Update id 2;

デッドロックシーケンス: T1(u2)、T2(u1)、T1(u1)wait、T2(u2)wait

このデッドロックシーケンスは、各トランザクション内でIDの順序で厳密に更新することで回避できます(同じパスで同じ順序でロックを取得します)。

クラスター化インデックスを使用する場合、キーの排他ロックと、更新時にRIDの排他ロックがあります。非クラスター化インデックスが使用されている場合、2つの異なるRIDに2つの排他ロックがあり、混乱を招きます。

一意のクラスター化インデックスがオンのid場合、行内データへの書き込みを保護するために、クラスタリングキーで排他ロックがかけられます。RIDLOB text列への書き込みを保護するには、別の排他ロックが必要です。LOB 列は、デフォルトで別のデータページに保存されます。

テーブルが、非クラスター化インデックスのみがオンのヒープである場合id、2つのことが起こります。最初の1つRID排他ロックはヒープの行内データに関連しは以前と同様のLOBデータのロックです。2番目の効果は、より複雑な実行計画が必要になることです。

クラスター化インデックスと単純な単一値の等価述語の更新により、クエリプロセッサは、単一のパスを使用して、単一の演算子で更新(読み取りおよび書き込み)を実行する最適化を適用できます。

単一オペレーターの更新

行は、排他ロックのみを必要とする単一のシーク操作で検索および更新されます(更新ロックは不要です)。サンプルテーブルを使用したロックシーケンスの例:

acquiring IX lock on OBJECT: 6:992930809:0 -- TABLE
acquiring IX lock on PAGE: 6:1:59104 -- INROW
acquiring X lock on KEY: 6:72057594233618432 (61a06abd401c) -- INROW
acquiring IX lock on PAGE: 6:1:59091 -- LOB
acquiring X lock on RID: 6:1:59091:1 -- LOB

releasing lock reference on PAGE: 6:1:59091 -- LOB
releasing lock reference on RID: 6:1:59091:1 -- LOB
releasing lock reference on KEY: 6:72057594233618432 (61a06abd401c) -- INROW
releasing lock reference on PAGE: 6:1:59104 -- INROW

非クラスター化インデックスのみでは、1つのBツリー構造から読み取り、別のBツリー構造を書き込む必要があるため、同じ最適化を適用できません。マルチパスプランには、読み取りフェーズと書き込みフェーズがあります。

マルチイテレーターの更新

これにより、読み取り時に更新ロックが取得され、行が条件を満たしている場合は排他ロックに変換されます。指定されたスキーマを使用したロックシーケンスの例:

acquiring IX lock on OBJECT: 6:992930809:0 -- TABLE
acquiring IU lock on PAGE: 6:1:59105 -- NC INDEX
acquiring U lock on KEY: 6:72057594233749504 (61a06abd401c) -- NC INDEX
acquiring IU lock on PAGE: 6:1:59104 -- HEAP
acquiring U lock on RID: 6:1:59104:1 -- HEAP
acquiring IX lock on PAGE: 6:1:59104 -- HEAP convert to X
acquiring X lock on RID: 6:1:59104:1 -- HEAP convert to X
acquiring IU lock on PAGE: 6:1:59091 -- LOB
acquiring U lock on RID: 6:1:59091:1 -- LOB

releasing lock reference on PAGE: 6:1:59091 
releasing lock reference on RID: 6:1:59091:1
releasing lock reference on RID: 6:1:59104:1
releasing lock reference on PAGE: 6:1:59104 
releasing lock on KEY: 6:72057594233749504 (61a06abd401c)
releasing lock on PAGE: 6:1:59105 

LOBデータが読み取られることに注意してください、テーブル更新イテレーターで。より複雑な計画と複数の読み取りおよび書き込みパスにより、デッドロックの可能性が高まります。

最後に、テーブル定義で使用されているデータ型に注意するしかありません。text新しい作業に非推奨のデータ型を使用しないでください。この列に最大2GBのデータを保存する機能が本当に必要な場合の代替手段はvarchar(max)です。間の1つの重要な相違点textvarchar(max)することであるtextデータがオフ行デフォルトで記憶され、一方varchar(max)で行デフォルトで記憶します。

Unicodeタイプを使用するのは、その柔軟性が必要な場合のみです(たとえば、IPアドレスにUnicodeが必要な理由がわかりにくい場合など)。また、属性に適切な長さの制限を選択します-どこでも255が正しいとは思えません。

補足
資料デッドロックとライブロックの一般的なパターン
Bart Duncanのデッドロックトラブルシューティングシリーズ

ロックのトレースは、さまざまな方法で実行できます。SQL Server Express with Advanced Services2014および2012 SP1以降のみ)には、ロックの取得と解放の詳細を表示するためのサポートされている方法であるプロファイラーツールが含まれています。


素晴らしい答え。「ロックを取得しています...」および「ロック参照を解放しています」というメッセージを含むログ/トレースをどのように出力していますか?
サンジフジバン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.