web-dev-qa-db-ja.com

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

私は主に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に大きなデータをプッシュするための間違いなく最速の方法であるため、意味がありません。

12
Alexei

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

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

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

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

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

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

2。可能であればTABLOCKを使用してください

テーブルにデータを挿入するセッションが1つだけであることが保証されている場合は、BULK INSERTTABLOCK引数を指定できます。これにより、ロックの競合が減少し、一部のシナリオでは 最小限のロギング が発生する可能性があります。ただし、既にデータが含まれているクラスター化インデックスを含むテーブルに挿入しているため、この回答で後述するトレースフラグ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の読み取りが報告されていますが、これは実用的な情報ではありません。場合によっては、最小限のロギングでレポートされた読み取りの数を減らすことができますが、その違いを直接パフォーマンスの向上に変換することはできません。

14
Joe Obbish

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

  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は、合計サイズが少なくとも1エクステント(8ページ)で、250行を超える必要があります。

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

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

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

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

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

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

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

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

  10. 名前付きパイプを使用して、ローカルホストとして実行してみてください。

  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 Monitor 、SolarWinds Database Performance Analyzer、またはGlen BarryのSQL Server診断クエリ、または Paul Randalの待機統計クエリ

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

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

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

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

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

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

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

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

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

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

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

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

  18. C#またはEntityFramework.Extensions(内部でSqlBulkCopyを使用)でSqlBulkCopyを使用している場合は、ビルド構成を確認します。テストをリリースモードで実行していますか?ターゲットアーキテクチャは任意の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. COLUMNSTOREINSERT/UPDATEパフォーマンス

12
John Zabroski

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

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

2
user126897