web-dev-qa-db-ja.com

SQL ServerでのLOBデータのDELETEが遅い

ロギング用のテーブルと、veryDELETEのパフォーマンスが非常に遅い古いデータをパージするためのストアドプロシージャがあります。テーブルまたはDELETEステートメントを変更して、LOBデータに対して適切に実行する方法を探しています。または、Microsoftから問題の承認がある場合-「SQLサーバーのバージョンxでこれを修正しました」、または「これはパフォーマンスが悪いと思われますが、優先順位ではありません」のようなものでも、それはうまくいきます。

これは、Microsoft SQL Server 2012(SP3)で実行されています。以下は実質的に私の実際のテーブルとコードですが、わずかに単純化されています:

CREATE TABLE [LOG_VALUE](
 [ID] [int] IDENTITY(1,1) NOT NULL,
 [VALUE] [varchar](max) NOT NULL,
 [CHECKSUM] [int] NOT NULL,
 [VALUE_LEN] [int] NOT NULL,
 CONSTRAINT [PK_LOG_REQUEST] PRIMARY KEY CLUSTERED ([ID] ASC)
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

問題の削除はこれで行われます:

WHILE (@@ROWCOUNT > 0)
  DELETE TOP (100) [LOG_VALUE]
  OUTPUT DELETED.[VALUE_LEN] INTO @DELETED_ROWS
  WHERE [ID] IN (SELECT [ID] FROM @DELETE_IDS);

基本的なストアドプロシージャプロセスは次のとおりです。

  1. 削除する行を参照する値を削除する
  2. 上記のログテーブルで存在しないLOBテーブルから[ID]値を選択し、テーブル変数@DELETE_IDSに入れます。
  3. 一度に上位100行を削除(競合/ロックを削減/防止するため)

LOBデータを削除するには、LOBデータをサーバーのメモリにロードする必要があるようです。サポート:1)テーブルに12,000行を挿入すると、ほぼ瞬時に2)12,000行を取得するのがほぼ瞬時に3)それらがキャッシュに存在しなくなると、同じ12,000行を選択して削除すると、約40時間がかかります。秒。

私が見つけることができた最も関連する質問は次のとおりです: https://serverfault.com/questions/241893/delete-performance-for-lob-data-in-sql-server 2011から、しかし悲しいことに満足のいく答えはありません。これをdba.stackexchange.comで尋ねると、より知識のあるオーディエンスが見つかることを願っています:)その質問の著者は間違いなく私よりもこれについて知っていましたが、私が読んだときの中心的なポイントは次のとおりです。

  1. 行を削除するとき、SQLサーバーは通常、行を削除済みとしてマークし、実際には後で「ゴーストクリーンアップタスク」によってクリーンアップされます
  2. lOBデータを削除すると、削除中にすべてのLOBデータページに排他ロックが設定され、ページの割り当てが解除されます
  3. ページがバッファキャッシュにない場合、これらの排他ロックは、ページがメモリにロードされるのを待ちます。
  4. 割り当て解除されるこれらのページは、クリーンアップタスクではなく、「前もって」発生します。つまり、削除操作は、ステートメントが終了する前にデータがロードされるのを常に待ち、関連する行のロックを解除します

この割り当て解除は、クリーンアップタスクで行われるべきであり、事前に行われるべきではないようです。また、LOBデータは述部(またはOUTPUT)で参照されないため、キャッシュにロードする必要はまったくありません。

関連があるかもしれないより多くの情報が、私はそれがそうではないと思います:

  • 値は多くの場合100KBを超えますが、サイズが大幅に異なります
  • [LOG_VALUE]。[ID]をポイントする他のテーブルは、参照整合性では適用されません
  • @DELETED_ROWSテーブルは、ストアドプロシージャの最後に、削除された行とバイトの数を記録するために蓄積されます。
  • 現在、110万行あり、IDENTITYの値により、160万行しかありませんでした。
  • トリガーはありません
  • 現在、ゴーストレコード数= 0
  • READ_COMMITTED_SNAPSHOTおよびALLOW_SNAPSHOT_ISOLATIONは両方ともオフです。

データベース全体(および同じサーバー上の他のデータベース)は、LOBデータの削除/更新以外のすべての場合に期待どおりに動作します。

7
Abacus

...それらがキャッシュに存在しなくなると、同じ12,000行を選択して削除するのに約40秒かかります。

これは、ストレージサブシステムが不十分であることを示しているようです。これが原因である場合、SQL ServerはおそらくPAGEIOLATCH_XX待機タイプ。

LOBデータを削除するには、LOBデータをサーバーのメモリにロードする必要があるようです。

Paul Randalが言ったと報告されているように(更新2、リンクした質問では)、SQL Server must LOBツリーをトラバースしてlocate the pagesでなければなりません削除されました。これを行うには、SQL ServerがこれらのLOBツリーページをメモリに取り込む必要があります。これを回避する方法は本当にありません。情報は他の方法では利用できません。

この割り当て解除は、クリーンアップタスクで行われるべきであり、事前に行われるべきではないようです。

これが機能する方法は、十分に文書化されていません。

私のテストでは、SQL Serverは、行を保持する特別な理由がない場合、LOBデータをすぐに削除します。これは最適化のようです。結局のところ、既にLOBデータを操作している場合は、そこにいる間に削除することもできます。ゴーストクリーンアップ(GC)に延期しても、GCはチェーンを走査する必要があるため、オーバーヘッドが増えるだけです。さらに、ログが削減されます。これは、SQL Serverが割り当て解除を記録するだけでよく、完全なLOBコンテンツは記録しないためです。

(たとえば)テーブルにトリガーがある場合、または行バージョンの分離レベルが有効になっている場合、LOB削除操作はdeferredto GCです。これらの機能はどちらも行バージョンを使用しており、LOB行で維持する必要があります。これらの場合、LOB削除は完全にログに記録されます。 GCが後で実行されるときに、LOBページの割り当て解除が実行されます。 ( documented )グローバルトレースフラグ661を使用してGCを一時的に無効にして、自分でテストする場合にこれらのゴースト化されたLOBレコードを表示できます。

これらすべてを考慮すると、削除をログに記録し、GCに対して既に行われた同じ作業の繰り返しを(完全に)延期することがどのように役立つかを理解するのは困難です。削除中に記録するログの量を減らしてLOBデータを削除する方が高速です。


質問には、削除パフォーマンスの低下の正確な原因について推測するだけでは不十分です。圧倒されたり、指定が不十分だったりするストレージシステムの可能性はありませんが、一般的には次のことをお勧めします。

  1. コミットされていない読み取り分離の下でLOBデータにアクセスしない
  2. 削除を実行するときに、LOBデータを含むテーブルにトリガーを設定しない
  3. 行のバージョン管理の副作用に注意してください
  4. ストレージサブシステムがロギングと一般的なI/Oに十分であることを確認する
  5. SQL Serverの最新ビルドを実行します(現在、SQL Server 2012の場合は11.00.6523)

ポイント1の詳細については、Paul Randalによる パフォーマンスのバグ:行外LOBデータを含むNOLOCKスキャン を参照してください。ポイント2と3の詳細については、 ページを分割する削除と転送されたゴースト (私による)を参照してください。

同様に、単純化された例では重要な詳細が欠落している、テーブル変数が非効率的な実行計画を作成している、またはテーブルに数十億のゴーストLOBレコードがすでに存在している可能性があります。詳細な分析には、おそらくシステム自体へのアクセスが必要になります。

8
Paul White 9