web-dev-qa-db-ja.com

HOLDLOCKはUPDLOCKにどのような影響がありますか?

UPDLOCKと組み合わせて使用​​されているHOLDLOCKヒントの例を多数見ました( これのように )。ただし、UPDLOCKは既にトランザクションの終了までロックを保持しているため、 Microsoftのドキュメント これらのヒントでは、HOLDLOCKは冗長であるように見えます。 (ともかく、HOLDLOCKはとにかく共有ロックにのみ適用されると言われているようです。)

HOLDLOCKはクエリにどのように影響しますか?

30
marijne

大きな影響があります。

更新ロックは、行の更新ロック、ページのインテント更新、およびテーブル/データベースの共有ロックを取得します。

ページ/データベースのロックは純粋に共有ロックであるため、これによって他のクエリがテーブル内のデータにアクセスするのを妨げることはありません。ロックと矛盾する操作を実行しようとしても、個々の行/ページ/テーブルに対してロックを衝突させることはありません。それが発生した場合、要求は現在のロックの後ろにキューイングされ、処理が進む前に使用可能になるのを待ちます。

Holdlockを使用することにより、クエリは強制的にシリアル化され、アクションが完了するまでテーブルを排他的にロックします。これにより、nolockヒントが使用されない限り、誰もテーブルを読み取ることができなくなり、ダーティリードの可能性があります。

効果を確認するには、サンプルテーブル 'foo'を生成し、ゴミ箱データをいくつか入れます。

begin tran

select * from foo with (updlock)
where tableid = 1
-- notice there is no commit tran

別のウィンドウを開いてみてください:

select * from foo

行が戻ってきたので、元のクエリトランザクションをコミットします。ホールドロックも使用するように変更して再実行します。

begin tran

select * from foo with (updlock, holdlock)
where tableid = 1

他のウィンドウに戻ってデータをもう一度選択してください。クエリは値を返しません。排他ロックによってブロックされているためです。最初のウィンドウでトランザクションをコミットすると、ブロックされなくなったため、2番目のクエリの結果が表示されます。

最後のテストでは、nolockを使用し、updlockとholdlockを使用してトランザクションを再度実行します。次に、2番目のウィンドウで次のコマンドを実行します。

select * from foo (nolock)

ダーティリード(コミットされていないリード)のリスクを受け入れたため、結果は自動的に返されます。

そのため、そのテーブルに対するアクションを強制的にシリアル化する必要があるか(行われる更新に応じて)、そのテーブルに非常に大きなボトルネックが作成されるため、大きな影響があると考えられます。だれもがトランザクションが長時間実行されているビジーなテーブルに対してそれを行った場合、アプリケーション内で大幅な遅延が発生します。

すべてのSQL機能と同様に、正しく使用すると強力になりますが、機能/ヒントの誤用は重大な問題を引き起こす可能性があります。デフォルトのアプローチとしてではなく、エンジンをオーバーライドする必要がある場合の最後の手段としてヒントを使用することを好みます。

要求どおりに編集:SQL 2005、2008、2008R2(すべてのエンタープライズ)でテスト-すべてがほぼデフォルトの設定でインストールされ、すべてのデフォルトを使用して作成されたテストデータベース(DBの名前のみを入力)。

63
Andrew

Andrewの回答はMSDNドキュメントに従って正しいですが、2008R2および2012に対してテストしましたが、この動作は見られないので、テストしてください

私が見ている行動は以下の通りです:

まず、これをプレイデータベースで実行します。

CREATE TABLE [dbo].[foo](
    [tableid] [int] IDENTITY(1,1) NOT NULL,
    [Col2] [varchar](100) NOT NULL,
    CONSTRAINT [PK_foo] PRIMARY KEY CLUSTERED 
    (
        [tableid] ASC
    )
)

...そして、数行を入れます。

次に、このコードを2つのクエリタブに貼り付けます(タブ2の「タブ1」のテキストを変更します)。

begin tran

select * from foo with (UPDLOCK, HOLDLOCK)
where tableid = 1

UPDATE foo SET Col2 = 'tab one'
where tableid = 1

commit tran

そして、これを別のtabに入れます:

select * from foo
where tableid = 1
  1. テーブルがあるプレイデータベースをポイントしていることを確認してください。

  2. everything BEFOREtab 1のupdateステートメントを強調表示して実行します。

  3. 同じことをタブ2でも行います。タブ2は完了せず、まだ実行中です。

  4. タブ私の環境では完了する]で単純なSELECTを実行します。

  5. tab 1のupdateステートメントを強調表示して実行します(まだコミットは行わないでください)。タブ2がまだ実行中であることがわかります。

  6. タブ1 ...タブ2でコミットを実行すると、選択が完了します...残りを実行できます。

13
Darren