web-dev-qa-db-ja.com

bcp / BULK INSERTとテーブル値パラメーターのパフォーマンス

スキーマが変更されたため、SQL ServerのBULK INSERTコマンドを使用して古いコードを書き換える必要があり、TVPを使用してストアドプロシージャに切り替えることを検討する必要があると思いましたが、パフォーマンスにどのような影響があるのだろうかと思っています。

私がこの質問をする理由を説明するのに役立つかもしれないいくつかの背景情報:

  • データは、実際にはWebサービスを介して受信されます。 Webサービスは、データベースサーバー上の共有フォルダーにテキストファイルを書き込み、データベースサーバーはBULK INSERTを実行します。このプロセスはもともとSQL Server 2000に実装されていましたが、当時は実際に元のプロセスであり、パフォーマンスの障害であった数百個のINSERTステートメントをサーバーに保持する以外に選択肢はありませんでした。

  • データは永続的なステージングテーブルに一括挿入されてから、はるかに大きなテーブルにマージされます(その後、ステージングテーブルから削除されます)。

  • 挿入するデータの量は「大」ですが、「巨大」ではありません-通常は数百行で、まれに5〜10k行です。したがって、私の直感では、BULK INSERTはログに記録されない操作であっても、that大きな違いはありません(もちろん、私はよくわからないので、質問)。

  • 挿入は、実際には非常に大きなパイプラインバッチプロセスの一部であり、何度も連続して実行する必要があります。したがって、パフォーマンスis critical。

BULK INSERTをTVPに置き換えたい理由は次のとおりです。

  • NetBIOSを介したテキストファイルの書き込みは、おそらくすでにある程度のコストがかかり、アーキテクチャの観点からはかなり厄介です。

  • ステージングテーブルは削除できる(および削除する必要がある)と考えています。主な理由は、挿入されたデータを挿入と同時に他のいくつかの更新に使用する必要があり、ほとんど空のステージングを使用するよりも大規模な本番テーブルから更新を試みる方がはるかにコストがかかることですテーブル。 TVPの場合、パラメーターは基本的にisステージングテーブルであり、メイン挿入の前後に任意の処理を実行できます。

  • 重複チェック、クリーンアップコード、および一括挿入に関連するすべてのオーバーヘッドをほとんどなくすことができました。

  • サーバーがこれらのトランザクションのいくつかを一度に取得する場合、ステージングテーブルまたはtempdbのロック競合を心配する必要はありません(回避しようとしますが、発生します)。

私は明らかにこれをプロダクションに投入する前にプロファイルしますが、その時間を過ごす前にまず尋ねて、誰かがこの目的でTVPを使用することについて厳しい警告があるかどうかを確認することは良い考えだと思いました。

だから-SQL Server 2008で十分に居心地がよく、これを試してみたか、少なくとも調査したことがある人にとって、評決とは何でしょうか?たとえば、数百行から数千行の挿入がかなり頻繁に発生する場合、TVPはマスタードをカットしますか?一括挿入と比較してパフォーマンスに大きな違いはありますか?


更新:疑問符が92%減りました!

(別名:テスト結果)

最終的な結果は、36段階の展開プロセスのように感じられるものの後、実稼働になりました。両方のソリューションを広範囲にテストしました:

  • 共有フォルダーのコードを取り出し、SqlBulkCopyクラスを直接使用します。
  • TVPを使用するストアドプロシージャへの切り替え。

読者がwhatが正確にテストされたというアイデアを得ることができるように、このデータの信頼性に関する疑問を和らげるために、ここでより詳細な説明がありますこのインポートプロセスが実際に行うこと

  1. 通常は約20〜50個のデータポイントである一時的なデータシーケンスから開始します(ただし、数百個になることもあります)。

  2. ほとんどの場合、データベースに依存しない、非常に多くのクレイジーな処理を実行します。このプロセスは並列化されているため、(1)の約8〜10個のシーケンスが同時に処理されています。各並列プロセスは、3つの追加シーケンスを生成します。

  3. 3つのシーケンスすべてと元のシーケンスを取り、それらをバッチに結合します。

  4. 終了した8〜10個のすべての処理タスクのバッチを1つの大きなスーパーバッチに結合します。

  5. BULK INSERT戦略(次の手順を参照)またはTVP戦略(手順8に進んでください)を使用してインポートします。

  6. SqlBulkCopyクラスを使用して、スーパーバッチ全体を4つの永続的なステージングテーブルにダンプします。

  7. (a)いくつかのJOIN条件を含む2つのテーブルで一連の集約ステップを実行し、次に(b)両方を使用して6つの実稼働テーブルでMERGEを実行するストアドプロシージャを実行します。集約および非集約データ。 (終了)

    [〜#〜] or [〜#〜]

  8. マージするデータを含む4つのDataTableオブジェクトを生成します。そのうち3つには、残念ながらADO.NET TVPで適切にサポートされていないCLR型が含まれているため、文字列表現として押し込まなければならず、パフォーマンスが少し低下します。

  9. TVPをストアドプロシージャにフィードします。ストアドプロシージャは(7)と本質的に同じ処理を行いますが、受信したテーブルを直接使用します。 (終了)

結果はかなり近いものでしたが、データがわずかに1000行を超えた場合でも、最終的にTVPアプローチの方が平均して優れたパフォーマンスを発揮しました。

このインポートプロセスは何千回も連続して実行されるため、すべてのマージの完了にかかった時間(はい、時間)を数えるだけで平均時間を取得するのは非常に簡単でした。

もともと、平均的なマージが完了するまでにほぼ8秒かかりました(通常の負荷の下)。 NetBIOSクラッジを削除してSqlBulkCopyに切り替えると、時間はほぼ正確に7秒に短縮されました。 TVPに切り替えると、時間はさらに短縮され、バッチあたり5.2秒になりました。これは5%の改善であり、実行時間が数時間で測定されるプロセスのスループットであるため、まったく悪くありません。また、SqlBulkCopyと比較して〜25%向上しています。

実際、私は実際の改善がこれよりもはるかに大きいと確信しています。テスト中に、最終的なマージがクリティカルパスではなくなったことが明らかになりました。代わりに、すべてのデータ処理を行っていたWebサービスは、着信するリクエストの数に応じて動作し始めていました。CPUもデータベースI/Oも実際には限界に達しておらず、重要なロックアクティビティもありませんでした。場合によっては、連続するマージ間に数秒のアイドル秒のギャップが見られました。わずかなギャップがありましたが、SqlBulkCopyを使用した場合ははるかに小さくなりました(0.5秒程度)。しかし、私はそれが別の日の物語になると思います。

結論:テーブル値パラメーターは、中規模のデータセットで動作する複雑なインポート+変換プロセスのBULK INSERT操作よりも実際に優れたパフォーマンスを発揮します。


私はもう1つポイントを付け加えたいと思います。プロステージングテーブルである人々の不安を和らげるためだけです。ある意味では、このサービス全体が1つの巨大なステージングプロセスです。プロセスのすべてのステップは厳重に監査されているため、特定のマージが失敗した理由を判断するためにステージングテーブルをneedしません(実際にはほとんどありませんが)起こります)。行う必要があるのは、サービスにデバッグフラグを設定することだけです。これにより、デバッガーにブレークするか、データベースではなくファイルにデータをダンプします。

つまり、プロセスについて十分な洞察を既に得ているため、ステージングテーブルの安全性は必要ありません。最初にステージングテーブルを用意した唯一の理由は、それ以外の場合に使用する必要があったすべてのINSERTおよびUPDATEステートメントでスラッシングを回避するためです。元のプロセスでは、ステージングデータは1秒未満のステージングテーブルにしか存在しなかったため、メンテナンス/保守性の観点からは価値がありませんでした。

また、notがすべてのBULK INSERT操作をTVPに置き換えていることに注意してください。大量のデータを処理したり、DBでデータをスローする以外にデータで特別なことをする必要のない操作は、SqlBulkCopyを使用します。 TVPがパフォーマンスの万能薬であることを示唆しているわけではありません。初期ステージングと最終マージの間のいくつかの変換を含むこの特定のインスタンスでSqlBulkCopyを超えて成功したことだけです。

そちらにあります。ポイントは最も関連性の高いリンクを見つけるためにTToniに行きますが、他の回答も感謝します。再度、感謝します!

75
Aaronaught

私はまだTVPの経験がありませんが、MSDNにニースのパフォーマンス比較チャートとバルク挿入があります こちら

彼らは、BULK INSERTの起動コストは高いが、その後は速いと言っています。リモートクライアントシナリオでは、約1000行に線を引きます(「単純な」サーバーロジックの場合)。それらの説明から判断すると、TVPを使用しても問題ないはずです。パフォーマンスヒット(あるとしても)はおそらく無視でき、アーキテクチャ上の利点は非常に良いようです。

編集:サイドノートでは、サーバーローカルファイルを回避し、SqlBulkCopyオブジェクトを使用して一括コピーを使用できます。 DataTableにデータを入力し、それをSqlBulkCopyインスタンスの「WriteToServer」メソッドにフィードするだけです。使いやすく、非常に高速です。

9
TToni

@TToniの回答で提供されているリンクに関して言及されているチャートは、文脈に沿って解釈する必要があります。これらの推奨事項に実際にどの程度の研究が費やされたかはわかりません(また、チャートはそのドキュメントの_2008_および_2008 R2_バージョンでのみ利用できるようです)。

一方、SQL Serverカスタマーアドバイザリーチームによる次のホワイトペーパーがあります。 TVPによるスループットの最大化

私は2009年以来TVPを使用しており、少なくとも私の経験では、追加のロジックを必要とせずに宛先テーブルへの単純な挿入以外の場合(これはめったにないケースです)、TVPが通常より良いオプションであることを発見しました。

データの検証はアプリ層で行う必要があるため、テーブルのステージングは​​避ける傾向があります。 TVPを使用することで簡単に対応でき、ストアドプロシージャのTVPテーブル変数は、その性質上、ローカライズされたステージングテーブルです(したがって、ステージングに実際のテーブルを使用する場合のように、同時に実行される他のプロセスと競合しません) )。

質問で行われたテストに関しては、最初に見つかったものよりもさらに高速であることが示されると思います。

  1. アプリケーションがTVPに値を送信する以外に使用しない限り、DataTableを使用しないでください。 _IEnumerable<SqlDataRecord>_インターフェースを使用すると、コレクションをメモリに複製してDBに送信するだけではないため、より高速でメモリ使用量が少なくなります。これは次の場所に文書化されています。
  2. TVPはテーブル変数であるため、統計を保持しません。つまり、クエリオプティマイザーに1行しかないことを報告します。そのため、procで次のいずれかを実行します。
    • 単純なSELECT以外の目的でTVPを使用するクエリでは、ステートメントレベルの再コンパイルを使用します。OPTION (RECOMPILE)
    • ローカル一時テーブル(つまり、単一の_#_)を作成し、TVPの内容を一時テーブルにコピーします
6
Solomon Rutzky

私はまだ一括挿入アプローチに固執すると思います。妥当な数の行を持つTVPを使用して、tempdbがまだヒットしている場合があります。これは私の直感です。TVPを使用したパフォーマンスをテストしたとは言えません(他の人の入力も聞きたいです)

.NETを使用するかどうかは言及しませんが、以前のソリューションを最適化するために取ったアプローチは、 SqlBulkCopy クラスを使用してデータのバルクロードを行うことでした-書く必要はありませんロードする前に最初にデータをファイルに保存し、 SqlBulkCopy クラス(たとえば)にDataTableを指定します。これは、DBにデータを挿入する最も速い方法です。 5〜10K行はそれほど多くありません。最大750K行でこれを使用しました。一般に、数百行あれば、TVPを使用しても大きな違いは生じないと思われます。しかし、スケールアップは私見に制限されます。

おそらく、SQL 2008の新しい [〜#〜] merge [〜#〜] 機能があなたに利益をもたらすでしょうか?

また、既存のステージングテーブルがこのプロセスの各インスタンスに使用される単一のテーブルであり、競合などが心配な場合は、毎回新しい「一時的」で物理的なステージングテーブルを作成し、それが終わったら削除することを検討してくださいで終わった?

このステージングテーブルへの読み込みを最適化できることに注意してください。インデックスを使用せずにテーブルを作成します。その後、データが設定されたら、その時点で必要なインデックスを追加します(この時点では更新されないため、最適な読み取りパフォーマンスのためにFILLFACTOR = 100)。

4
AdaTheDev

ステージングテーブルは優れています!本当に他の方法でやりたくはありません。どうして?データのインポートは予期せずに変化する可能性があるため(また、多くの場合、列が名と姓と呼ばれていても、姓の列に名のデータが含まれているなど、予測できない方法で、例を挙げないためステージングテーブルを使用して問題を簡単に調査できるため、インポートが処理した列に含まれるデータを正確に確認できます。あなたがメモリ内のテーブルを使用すると、私は見つけるのが難しいと思います。私は、私と同じように生計のために輸入をしている多くの人々を知っており、彼ら全員がステージングテーブルの使用を推奨しています。これには理由があると思う。

作業プロセスに対する小さなスキーマの変更をさらに修正することは、プロセスを再設計するよりも簡単で時間もかかりません。それが機能していて、誰もそれを変更するのに何時間も支払う気がないなら、スキーマの変更のために修正する必要があるものだけを修正します。プロセス全体を変更すると、テスト済みの既存の作業プロセスに小さな変更を加えるよりも、潜在的な新しいバグを導入できます。

そして、どのようにしてすべてのデータクリーンアップタスクを廃止するのですか?あなたはそれらを異なってやっているかもしれませんが、彼らはまだやる必要があります。繰り返しますが、あなたが説明する方法でプロセスを変更することは非常に危険です。

個人的には、新しいおもちゃで遊ぶ機会を得るのではなく、古い技術を使っているだけで気分を害しているように思えます。一括挿入が2000である以外は、変更したいという本当の根拠はないようです。

0
HLGEM