BULK INSERTステートメントのパフォーマンスをどのように調査しますか?


12

私は主にEntity Framework ORMを使用する.NET開発者です。ただし、ORMの使用に失敗したくないので、データレイヤー(データベース)内で何が起こるかを理解しようとしています。基本的に、開発中にプロファイラを起動し、クエリの観点からコードの一部が生成するものを確認します。

非常に複雑なもの(ORMが注意深く書かれていなければ、かなり単純なLINQステートメントからでもひどいクエリを生成する可能性があります)や重いもの(持続時間、CPU、ページの読み取り)を見つけた場合は、それをSSMSで受け取り、その実行計画を確認します。

私のデータベースの知識レベルでは問題なく動作します。ただし、BULK INSERTはSHOWPLANを生成しないように見えるため、特別な生物のようです

非常に単純な例を示します。

テーブル定義

CREATE TABLE dbo.ImportingSystemFileLoadInfo
(
    ImportingSystemFileLoadInfoId INT NOT NULL IDENTITY(1, 1) CONSTRAINT PK_ImportingSystemFileLoadInfo PRIMARY KEY CLUSTERED,
    EnvironmentId INT NOT NULL CONSTRAINT FK_ImportingSystemFileLoadInfo REFERENCES dbo.Environment,
    ImportingSystemId INT NOT NULL CONSTRAINT FK_ImportingSystemFileLoadInfo_ImportingSystem REFERENCES dbo.ImportingSystem,
    FileName NVARCHAR(64) NOT NULL,
FileImportTime DATETIME2 NOT NULL,
    CONSTRAINT UQ_ImportingSystemImportInfo_EnvXIs_TableName UNIQUE (EnvironmentId, ImportingSystemId, FileName, FileImportTime)
)

注:他のインデックスはテーブルで定義されていません

一括挿入 (プロファイラーでキャッチするもの、1つのバッチのみ)

insert bulk [dbo].[ImportingSystemFileLoadInfo] ([EnvironmentId] Int, [ImportingSystemId] Int, [FileName] NVarChar(64) COLLATE Latin1_General_CI_AS, [FileImportTime] DateTime2(7))

指標

  • 695個のアイテムが挿入されました
  • CPU = 31
  • 読み取り= 4271
  • 書き込み= 24
  • 期間= 154
  • 合計テーブル数= 11500

私のアプリケーションではそれで問題ありませんが、読み取りはかなり大きいようです(SQL Serverの内部についてはほとんど知らないので、8Kのページサイズと持っている小さなレコード情報と比較しています)。

質問:この一括挿入を最適化できるかどうかを調べるにはどうすればよいですか?または、クライアントアプリケーションからSQL Serverに大きなデータをプッシュするための間違いなく最速の方法であるため、それは意味がありませんか?

回答:


14

私が知る限り、通常の挿入を最適化するのと非常によく似た方法で一括挿入を最適化できます。通常、単純な挿入のクエリプランはあまり有益ではないため、プランがないことを心配する必要はありません。挿入を最適化するいくつかの方法を説明しますが、それらのほとんどは、質問で指定した挿入には適用されない可能性があります。ただし、将来的に大量のデータをロードする必要がある場合に役立ちます。

1.クラスタリングキーの順序でデータを挿入する

SQL Serverは、クラスター化インデックスのあるテーブルにデータを挿入する前に、データをソートすることがよくあります。一部のテーブルやアプリケーションでは、フラットファイルのデータを並べ替え、SQL Serverにデータが次のORDER引数で並べ替えられていることを通知することで、パフォーマンスを向上させることができますBULK INSERT

ORDER({column [ASC | DESC]} [、... n])

データファイル内のデータの並べ替え方法を指定します。インポートされるデータがテーブルのクラスター化インデックス(存在する場合)に従って並べ替えられている場合、一括インポートのパフォーマンスが向上します。

IDENTITYクラスター化されたキーとして列を使用しているので、これについて心配する必要はありません。

2. TABLOCK可能であれば使用する

1つのセッションのみがテーブルにデータを挿入することが保証されている場合は、のTABLOCK引数を指定できますBULK INSERT。これにより、ロックの競合が減少し、一部のシナリオではログ最小限になる可能性があります。ただし、すでにデータが含まれているクラスター化インデックスを含むテーブルに挿入しているため、この回答で後述するトレースフラグ610がないと、最小限のログが得られません。

それTABLOCKが不可能な場合は、コードを変更できないため、すべての希望が失われるわけではありません。使用を検討してくださいsp_table_option

EXEC [sys].[sp_tableoption]
    @TableNamePattern = N'dbo.BulkLoadTable' ,
    @OptionName = 'table lock on bulk load' , 
    @OptionValue = 'ON'

別のオプションは、トレースフラグ715を有効にすることです。

3.適切なバッチサイズを使用する

バッチサイズを変更することで、挿入を調整できる場合があります。

ROWS_PER_BATCH = rows_per_batch

データファイル内のデータのおおよその行数を示します。

デフォルトでは、データファイル内のすべてのデータは単一のトランザクションとしてサーバーに送信され、バッチ内の行数はクエリオプティマイザーにとって不明です。ROWS_PER_BATCH(値> 0)を指定すると、サーバーはこの値を使用して一括インポート操作を最適化します。ROWS_PER_BATCHに指定する値は、実際の行数とほぼ同じにする必要があります。パフォーマンスの考慮事項については、このトピックで後述する「解説」を参照してください。

これは記事の後半からの引用です:

単一のバッチでフラッシュされるページ数が内部しきい値を超えると、バッチのコミット時にフラッシュするページを特定するために、バッファープールのフルスキャンが発生する可能性があります。このフルスキャンは、一括インポートのパフォーマンスを低下させる可能性があります。大きなバッファープールが低速のI / Oサブシステムと組み合わされている場合、内部しきい値を超える可能性が高くなります。大規模なマシンでのバッファオーバーフローを回避するには、TABLOCKヒントを使用しない(バルク最適化を削除する)か、より小さいバッチサイズ(バルク最適化を保持する)を使用します。

コンピュータはさまざまであるため、データの負荷でさまざまなバッチサイズをテストして、最適な方法を見つけることをお勧めします。

個人的には、1つのバッチに695行すべてを挿入するだけです。ただし、大量のデータを挿入する場合は、バッチサイズを調整すると大きな違いが生じる可能性があります。

4. IDENTITYカラムが必要であることを確認します

私はあなたのデータモデルや要件については何も知りませんがIDENTITY、すべてのテーブルに列を追加するという罠にはまりません。Aaron Bertrandはこれについて、悪い習慣と呼ばれる、すべてのテーブルにIDENTITY列を配置する記事を公開しています。明確にするために、IDENTITYこのテーブルから列を削除する必要があるとは言っていません。ただし、IDENTITY列が不要であると判断して削除すると、挿入のパフォーマンスが向上する可能性があります。

5.インデックスまたは制約を無効にする

既存のデータと比較して大量のデータをテーブルにロードする場合は、ロード前にインデックスまたは制約を無効にし、ロード後に有効にする方が速い場合があります。大量のデータの場合、通常、SQL Serverがデータをテーブルにロードするのではなく、一度にすべてのインデックスを作成する方が非効率的です。11500行のテーブルに695行を挿入したようですので、この手法はお勧めしません。

6. TF 610を検討する

トレースフラグ610を使用すると、いくつかの追加シナリオで最小限のログが可能になります。IDENTITYクラスター化されたキーを持つテーブルの場合、復旧モデルがシンプルまたは一括ログである限り、新しいデータページのログは最小限になります。一部のシステムではパフォーマンスが低下する可能性があるため、この機能はデフォルトでは無効になっていると思います。このトレースフラグを有効にする前に、慎重にテストする必要があります。推奨されるMicrosoftのリファレンスは、データロードパフォーマンスガイドであるようです。

トレースフラグ610での最小限のログ記録のI / Oへの影響

最小限に記録されたバルクロードトランザクションをコミットする場合、コミットが完了する前に、ロードされたすべてのページをディスクにフラッシュする必要があります。以前のチェックポイント操作でキャッチされなかったフラッシュされたページは、大量のランダムI / Oを作成する可能性があります。これを完全にログに記録された操作と比較してください。これは、代わりにログ書き込みで順次I / Oを作成し、コミット時にロードされたページをディスクにフラッシュする必要がありません。

ロードシナリオがチェックポイントの境界を越えないbtreeでの小さな挿入操作であり、I / Oシステムが遅い場合、最小限のロギングを使用すると、実際には挿入速度が遅くなる可能性があります。

私の知る限り、これはトレースフラグ610とは何の関係もありませんが、最小限のロギング自体では関係ありません。ROWS_PER_BATCHチューニングに関する以前の引用は、これと同じコンセプトに達していると思います。

結論として、を調整するためにできることはおそらく多くありませんBULK INSERT。私はあなたがあなたの挿入物で観察した読み取りカウントについて心配していません。SQL Serverは、データを挿入するたびに読み取りを報告します。次の非常に単純な例を考えますINSERT

DROP TABLE IF EXISTS X_TABLE;

CREATE TABLE X_TABLE (
VAL VARCHAR(1000) NOT NULL
);

SET STATISTICS IO, TIME ON;

INSERT INTO X_TABLE WITH (TABLOCK)
SELECT REPLICATE('Z', 1000)
FROM dbo.GetNums(10000); -- generate 10000 rows

からの出力SET STATISTICS IO, TIME ON

テーブル 'X_TABLE'。スキャンカウント0、論理読み取り11428

私は11428の読み取りを報告していますが、それは実用的な情報ではありません。場合によっては、最小限のロギングでレポートされる読み取りの数を減らすことができますが、その違いを直接パフォーマンスの向上に変換することはできません。


12

トリックのナレッジベースを構築する際に、この答えを継続的に更新するつもりで、この質問に答え始めます。うまくいけば、他の人がこれに遭遇し、その過程で自分の知識を向上させるのに役立ちます。

  1. ガットチェック:ファイアウォールはステートフルで詳細なパケット検査を行っていますか?これについてはインターネットではあまりわかりませんが、一括挿入が想定よりも約10倍遅い場合は、セキュリティアプライアンスがレベル​​3〜7のディープパケットインスペクションを実行し、「Generic SQL Injection Prevention」をチェックしている可能性があります。 」

  2. 一括挿入する予定のデータのサイズを、バッチごとにバイト単位で測定します。また、LOBデータを格納しているかどうかを確認します。これは、ページのフェッチと書き込みの操作が別々であるためです。

    このようにする必要があるいくつかの理由:

    a。AWSでは、Elastic Block Storage IOPSは行ではなくバイトに分解されます。

    1. EBS IOPSユニットの説明については、LinuxインスタンスでのAmazon EBSボリュームパフォーマンス»I / O特性とモニタリングを参照してください。
    2. 具体的には、汎用SSD(gp2)ボリュームには「I / Oクレジットとバーストパフォーマンス」の概念があり、大量のETL処理でバーストバランスクレジットを使い果たすことがよくあります。バースト期間は、SQL Server行ではなくバイト単位で測定されます:)

    b。ほとんどのライブラリまたはホワイトペーパーは行数に基づいてテストしますが、実際に書き込むことができるページ数であり、それを計算するには、行あたりのバイト数とページサイズ(通常は8KB)を知る必要があります、ただし、誰かが他の人からシステムを継承しているかどうかを常にダブルチェックしてください。)

    SELECT *
    FROM 
    sys.dm_db_index_physical_stats(DB_ID(),OBJECT_ID(N'YourTable'), NULL, NULL, 'DETAILED')

    avg_record_size_in_bytesとpage_countに注意してください。

    c。Paul Whiteがhttps://sqlperformance.com/2019/05/sql-performance/minimal-logging-insert-select-heapで説明しているように、「で最小限のロギングを有効にするにはINSERT...SELECT、SQL Serverは合計サイズで250行以上を想定する必要があります少なくとも1つのエクステント(8ページ)。」

  3. チェック制約または一意制約のあるインデックスがある場合は、SET STATISTICS IO ONand SET STATISTICS TIME ON(またはSQL Server ProfilerまたはSQL Server拡張イベント)を使用して、一括挿入に読み取り操作があるかどうかなどの情報を取得します。読み取り操作は、SQL Serverデータベースエンジンが整合性制約を確実に通過させるためです。

  4. PRIMARYFILEGROUPがRAMドライブにマウントされているテストデータベースを作成してみてください。これはSSDよりもわずかに高速ですが、RAIDコントローラーがオーバーヘッドを追加する可能性があるかどうかについての質問も排除します。2018年にはすべきではありませんが、このような複数の差分ベースラインを作成することで、ハードウェアがどれほどのオーバーヘッドを追加するかについての一般的なアイデアを得ることができます。

  5. また、ソースファイルもRAMドライブに配置します。

    ソースファイルをRAMドライブに置くと、データベースサーバーのFILEGROUPが存在するドライブと同じドライブからソースファイルを読み取る場合に、競合の問題が排除されます。

  6. 64KBのエクステントを使用してハードドライブをフォーマットしたことを確認します。

  7. UserBenchmark.comを使用してSSDをベンチマークします。この意志:

    1. デバイスに期待するパフォーマンスについて、他のパフォーマンス愛好家にさらに知識を追加する
    2. ドライブのパフォーマンスが、まったく同じドライブでピアのパフォーマンスが低いかどうかを判断するのに役立ちます
    3. ドライブのパフォーマンスが同じカテゴリの他のドライブ(SSD、HDDなど)を下回っているかどうかを判断するのに役立ちます
  8. Entity Framework Extensionsを介してC#から「INSERT BULK」を呼び出す場合は、最初にJITを「ウォームアップ」し、最初のいくつかの結果を「捨てる」ようにしてください。

  9. プログラムのパフォーマンスカウンターを作成してみてください。.NETでは、benchmark.NETを使用でき、一連の基本的なメトリックが自動的にプロファイリングされます。その後、プロファイラーの試行をオープンソースコミュニティと共有し、異なるハードウェアを実行している人々が同じメトリックを報告するかどうかを確認できます(つまり、UserBenchmark.comを使用した比較に関する以前のポイントから)。

  10. 名前付きパイプを使用して、それをlocalhostとして実行してみてください。

  11. SQL Serverをターゲットにして.NET Coreを使用している場合は、SQL Server Std EditionでLinuxを起動することを検討してください。これは、深刻なハードウェアでも1時間あたり1ドル未満のコストです。異なるOSの同じハードウェアで同じコードを試す主な利点は、OSカーネルのTCP / IPスタックが問題を引き起こしていないかどうかを確認することです。

  12. Glen BarryのSQL Server診断クエリを使用して、データベーステーブルのFILEGROUPを格納するドライブのドライブ遅延を測定します。

    a。テスト前とテスト後に必ず測定してください。「テスト前」は、ベースラインとして恐ろしいIO特性があるかどうかを示すだけです。

    b。「テスト中」を測定するには、PerfMonパフォーマンスカウンターを使用する必要があります。

    どうして?ほとんどのデータベースサーバーは何らかのネットワーク接続ストレージ(NAS)を使用しているためです。クラウド、AWSでは、Elastic Block Storageはまさにそれです。EBSボリューム/ NASソリューションのIOPSによって制約を受ける可能性があります。

  13. いくつかのツールを使用して、待機統計を測定します。 Red Gate SQLモニター、SolarWindsデータベースパフォーマンスアナライザー、さらにはグレンバリーのSQL Server診断クエリ、またはPaul Randalの待機統計クエリ

    a。最も一般的な待機タイプは、メモリ/ CPU、WRITELOG、PAGEIOLATCH_EX、およびASYNC_NETWORK_IOです。

    b。可用性グループを実行している場合は、追加の待機タイプが発生する可能性があります。

  14. 無効にした複数の同時INSERT BULKコマンドの影響を測定しTABLOCKます(TABLOCKはおそらくINSERT BULKコマンドのシリアル化を強制します)。ボトルネックがのINSERT BULK完了を待っている可能性があります。データベースサーバーの物理データモデルが処理できる数だけ、これらのタスクをキューに入れるようにしてください。

  15. テーブルを分割することを検討してください。特定の例として:データベーステーブルが追加専用の場合、Andrew Novickは「TODAY」FILEGROUPを作成し、少なくとも2つのファイルグループTODAYとBEFORE_TODAYにパーティション化することを提案しました。このようにして、INSERT BULKデータが今日のデータのみである場合、CreatedOnフィールドでフィルタリングして、すべての挿入を強制的に1つにヒットFILEGROUPさせることができTABLOCKます。これにより、を使用するときのブロックを減らすことができます。この手法については、Microsoftホワイトペーパー:SQL Server 2008を使用したパーティションテーブルとインデックス戦略で詳しく説明しています。

  16. TABLOCK列ストアインデックスを使用している場合は、データをオフにして、バッチサイズ102,400行のデータを読み込みます。次に、すべてのデータを列ストア行グループに直接並列でロードできます。この提案(および文書化された合理的)は、Microsoftの列ストアインデックス-データ読み込みガイダンスから来ています。

    一括読み込みには、次の組み込みのパフォーマンス最適化があります。

    並列読み込み:複数の同時一括読み込み(bcpまたは一括挿入)を使用して、それぞれが個別のデータファイルを読み込むことができます。SQL Serverへの行ストアの一括読み込みとは異なりTABLOCK、各一括インポートスレッドは、排他的ロックを使用してデータを個別の行グループ(圧縮行グループまたは差分行グループ)に排他的に読み込むため、指定する必要はありません。を使用TABLOCKすると、テーブルが排他的にロックされ、データを並行してインポートできなくなります。

    最小限のロギング:一括読み込みでは、圧縮された行グループに直接送られる最小限のデータロギングが使用されます。デルタ行グループに送られるデータはすべて完全にログに記録されます。これには、102,400行未満のバッチサイズが含まれます。ただし、一括読み込みの目標は、ほとんどのデータがデルタ行グループをバイパスすることです。

    ロックの最適化:圧縮された行グループにロードすると、行グループのXロックが取得されます。ただし、デルタ行グループに一括ロードする場合、行グループでXロックが取得されますが、X行グループロックはロック階層の一部ではないため、SQL Serverは引き続きロックPAGE / EXTENTをロックします。

  17. SQL Server 2016以降、インデックス付きテーブルへの最小限のログ記録のためにトレースフラグ610を有効にする必要がなくなりました。MicrosoftエンジニアのParikshit Savjani(強調鉱山)を引用すると:

    SQL Server 2016の設計目標の1つは、すぐに使えるエンジンのパフォーマンスとスケーラビリティを改善して、顧客がノブやトレースフラグを必要とせずに高速で実行できるようにすることでした。これらの改善の一環として、SQL Serverエンジンコードで行われた拡張機能の1つは、単純なデータベースまたは一括ログ復旧モデル。最小限のログ記録に慣れていない場合は、SQL Serverでの最小限のログ記録のしくみについて説明しているSunil Agrawalのこのブログ投稿を読むことを強くお勧めします。一括挿入のログを最小限に抑えるには、ここに記載されている前提条件を満たしている必要があります。

    SQL Server 2016でのこれらの機能強化の一部として、インデックス付きテーブルへの最小限のログ記録のためにトレースフラグ610を有効にする必要がなくなりましたまた、他のトレースフラグ(1118、1117、1236、8048)と結合して、履歴の一部になります。SQL Server 2016では、一括読み込み操作によって新しいページが割り当てられるときに、前述の最小限のログ記録に関する他のすべての前提条件が満たされている場合、その新しいページを順次埋めるすべての行が最小限のログに記録されます。インデックスの順序を維持するために既存のページ(新しいページ割り当てなし)に挿入された行は、ロード中にページ分割の結果として移動された行と同様に、完全にログに記録されます。また、割り当て時にページロックが取得され、ページまたはエクステントの割り当てのみがログに記録されるため、最小限のログ操作が機能するように、インデックスに対してALLOW_PAGE_LOCKSをONにすることも重要です(デフォルトではONです)。

  18. C#またはEntityFramework.Extensions(内部でSqlBulkCopyを使用)でSqlBulkCopyを使用している場合は、ビルド構成を確認します。テストをリリースモードで実行していますか?ターゲットアーキテクチャはAny CPU / x64 / x86に設定されていますか?

  19. sp_who2を使用して、INSERT BULKトランザクションがSUSPENDEDかどうかを確認することを検討してください。別のspidによってブロックされているため、中断されている可能性があります。SQL Serverのブロックを最小化する方法を読むこと検討してください。Adam Machanicのsp_WhoIsActiveを使用することもできますが、sp_who2は必要な基本情報を提供します。

  20. ディスクI / Oが不良である可能性があります。一括挿入を実行していて、ディスク使用率が100%に達しておらず、約2%で止まっている場合は、ファームウェアが不良か、I / Oデバイスに欠陥がある可能性があります。(これは私の同僚に起こりました。)[SSD UserBenchmark]を使用して、ハードウェアパフォーマンスについて他のユーザーと比較してください。特に、ローカルの開発マシンで低速を再現できる場合はそうです。(IPリスクのため、ほとんどの企業は開発者がローカルマシンでデータベースを実行することを許可していないため、これをリストの最後に配置します。)

  21. テーブルが圧縮を使用している場合は、複数のセッションを実行してみて、各セッションで、既存のトランザクション使用して開始し、SqlBulkCopyコマンドの前にこれを実行できます。

    ALTER SERVER CONFIGURATION SET PROCESS AFFINITY CPU = AUTO;

  22. 継続的な読み込みについては、最初にMicrosoftホワイトペーパーの「SQL Server 2008を使用したパーティションテーブルとインデックス戦略」で概説されている一連のアイデアです。

    連続ローディング

    OLTPシナリオでは、新しいデータが継続的に受信される場合があります。ユーザーが最新のパーティションにもクエリを実行している場合、データを継続的に挿入すると、ブロックが発生する可能性があります。ユーザークエリが挿入をブロックし、同様に、挿入がユーザークエリをブロックする場合があります。

    読み込み中のテーブルまたはパーティションの競合は、スナップショット分離、特にREAD COMMITTED SNAPSHOT分離レベルを使用することで減らすことができます。下でREAD COMMITTED SNAPSHOT隔離、テーブルへの挿入がで活動起こさないtempdbの各ので、バージョンストアのtempdbのオーバーヘッドは挿入のための最小限のですが、何の共有ロックが同じパーティションにユーザーのクエリによって取られません。

    他のケースでは、データがパーティションテーブルに高速で継続的に挿入されている場合でも、ステージングテーブルに短時間データをステージングし、そのウィンドウまでのデータを最新のパーティションに繰り返し挿入できる場合があります。現在のパーティションが通過し、データが次のパーティションに挿入されます。たとえば、交互に、それぞれ30秒に相当するデータを受け取る2つのステージングテーブルがあるとします。1つは1分前のテーブル、2つ目のテーブルは1分の後半です。挿入ストアドプロシージャは、現在の挿入が1分あたり何分かを判断し、最初のステージングテーブルに挿入します。30秒が経過すると、挿入プロシージャは、2番目のステージングテーブルに挿入する必要があると判断します。次に、別のストアドプロシージャが、最初のステージングテーブルからテーブルの最新のパーティションにデータを読み込み、最初のステージングテーブルを切り捨てます。さらに30秒後、同じストアドプロシージャが2番目のストアドプロシージャのデータを挿入して現在のパーティションに入れ、2番目のステージングテーブルを切り捨てます。

  23. Microsoft CATチームのデータ読み込みパフォーマンスガイド

  24. 統計が最新であることを確認してください。各インデックスの作成後に可能であれば、FULLSCANを使用します。

  25. SQLIOを使用したSANパフォーマンスチューニング。また、メカニカルディスクを使用している場合は、ディスクパーティションが調整されていることを確認してください。MicrosoftのDisk Partition Alignment Best Practicesを参照してください。

  26. COLUMNSTORE INSERT/ UPDATEパフォーマンス


2

読み取りは、挿入中にチェックされる一意の&FK制約である可能性があります。挿入中にそれらを無効化/削除して、後で有効化/再作成できると、速度が向上する可能性があります。これにより、アクティブにしておくよりも全体的に遅くなるかどうかをテストする必要があります。他のプロセスが同じテーブルに同時に書き込みを行っている場合も、これは良い考えではありません。- ガレス・ライオンズ

Q&Aによると、一括挿入後に外部キーが信頼できないようになるBULK INSERTと、CHECK_CONSTRAINTSオプションなしでFK制約が信頼できないようになります(信頼できない制約で終了したため、私の場合)。明確ではありませんが、チェックして信頼できないようにすることは意味がありません。ただし、PKとUNIQUEは引き続きチェックされます(BULK INSERT(Transact-SQL)を参照)。- アレクセイ

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.