57

C# クライアントが大量のデータを SQL Server 2005 データベースに挿入すると、パフォーマンスのボトルネックが発生し、プロセスを高速化する方法を探しています。

私はすでに SqlClient.SqlBulkCopy (TDS に基づく) を使用して、回線を介したデータ転送を高速化しており、これは非常に役立ちましたが、さらに多くのことを探しています。

次のような単純なテーブルがあります。

 CREATE TABLE [BulkData](
 [ContainerId] [int] NOT NULL,
 [BinId] [smallint] NOT NULL,
 [Sequence] [smallint] NOT NULL,
 [ItemId] [int] NOT NULL,
 [Left] [smallint] NOT NULL,
 [Top] [smallint] NOT NULL,
 [Right] [smallint] NOT NULL,
 [Bottom] [smallint] NOT NULL,
 CONSTRAINT [PKBulkData] PRIMARY KEY CLUSTERED 
 (
  [ContainerIdId] ASC,
  [BinId] ASC,
  [Sequence] ASC
))

ContainerId と BinId が各チャンクで一定で、シーケンス値が 0-n で、値が主キーに基づいて事前に並べ替えられている、平均約 300 行のチャンクにデータを挿入しています。

%Disk time パフォーマンス カウンターは 100% で多くの時間を費やしているため、ディスク IO が主な問題であることは明らかですが、得られた速度は未加工のファイル コピーよりも桁違いです。

次の場合に役立ちますか?

  1. 挿入中に主キーを削除し、後で再作成します
  2. 同じスキーマで一時テーブルに挿入し、定期的にメイン テーブルに転送して、挿入が行われるテーブルのサイズを小さく保ちます。
  3. 他に何か?

-- いただいた回答に基づいて、少し明確にさせてください。

Portman: データがすべてインポートされたら、その順序でデータに順次アクセスする必要があるため、クラスター化インデックスを使用しています。データのインポート中にインデックスが存在する必要は特にありません。インポートのために制約を完全に削除するのではなく、挿入中に非クラスター化 PK インデックスを使用する利点はありますか?

Chopeen: データは他の多くのマシンでリモートで生成されています (私の SQL サーバーは現在約 10 台しか処理できませんが、もっと追加できるようにしたいと思っています)。プロセス全体をローカル マシンで実行するのは現実的ではありません。出力を生成するために 50 倍の入力データを処理する必要があるからです。

Jason: インポート プロセス中にテーブルに対して同時クエリを実行していません。主キーを削除してみて、それが役立つかどうかを確認します。

4

8 に答える 8

20

SQL Server でインデックスを無効/有効にする方法は次のとおりです。

--Disable Index ALTER INDEX [IX_Users_UserID] SalesDB.Users DISABLE
GO
--Enable Index ALTER INDEX [IX_Users_UserID] SalesDB.Users REBUILD

解決策を見つけるのに役立つリソースを次に示します。

一括読み込み速度の比較

SqlBulkCopy を使用してクライアントから SQL Server にデータをすばやく読み込む

一括コピーのパフォーマンスの最適化

NOCHECK および TABLOCK オプションを必ず調べてください。

テーブル ヒント (Transact-SQL)

挿入 (Transact-SQL)

于 2010-05-24T18:58:43.250 に答える
18

すでにSqlBulkCopyを使用しています。これは良いスタートです。

ただし、SqlBulkCopyクラスを使用するだけでは、SQLが一括コピーを実行することを必ずしも意味するわけではありません。特に、SQLServerが効率的な一括挿入を実行するために満たす必要のある要件がいくつかあります。

参考文献:

好奇心から、なぜあなたのインデックスはそのように設定されているのですか?ContainerId / BinId / Sequenceは、非クラスター化インデックスに適しているようですこのインデックスをクラスター化する特別な理由はありますか?

于 2008-08-23T13:27:42.807 に答える
10

そのインデックスをnonclusteredに変更すると、劇的な改善が見られると思います。これにより、次の 2 つのオプションが残ります。

  1. インデックスを非クラスター化に変更し、クラスター化インデックスなしでヒープ テーブルのままにします
  2. インデックスを非クラスター化に変更しますが、代理キー (「id」など) を追加して、ID、主キー、およびクラスター化インデックスにします。

どちらも、読み取りを著しく遅くすることなく、挿入を高速化します。

このように考えてみてください。今、あなたは SQL に一括挿入を行うように指示していますが、SQL にテーブルを追加するたびにテーブル全体を並べ替えるように指示しています。非クラスター化インデックスでは、レコードを任意の順序で追加し、目的の順序を示す別のインデックスを作成します。

于 2008-08-23T20:37:47.283 に答える
5

トランザクションを使用してみましたか?

サーバーが100%の時間をディスクにコミットしていることから、アトミックSQL文で各データ行を送信しているように見えるため、サーバーはすべての行をコミット(ディスクに書き込む)する必要があります。

代わりにトランザクションを使用すると、サーバーはトランザクションの最後に一度だけコミットします。

詳細なヘルプ: サーバーにデータを挿入するためにどのような方法を使用していますか? DataAdapter を使用して DataTable を更新するか、文字列を使用して各文を実行しますか?

于 2008-08-23T13:10:32.847 に答える
3

BCP-設定するのは面倒ですが、DBの黎明期から存在しており、非常に迅速です。

この順序でデータを挿入しない限り、3部構成のインデックスは非常に遅くなります。後でそれを適用すると、物事も本当に遅くなりますが、2番目のステップになります。

SQLの複合キーは常に非常に遅く、キーが大きいほど遅くなります。

于 2008-08-23T18:14:25.737 に答える
3

私は本当に頭のいい人ではなく、SqlClient.SqlBulkCopy メソッドの経験もあまりありませんが、その価値を 2 セント挙げておきます。それがあなたや他の人に役立つことを願っています(または、少なくとも人々が私の無知を指摘する原因になります;)。

データベース データ ファイル (mdf) がトランザクション ログ ファイル (ldf) とは別の物理ディスク上にない限り、生ファイルのコピー速度に匹敵することはありません。さらに、より公正な比較のために、クラスター化されたインデックスも別の物理ディスク上にある必要があります。

生のコピーは、インデックス作成の目的で、選択したフィールド (列) の並べ替え順序をログに記録したり、維持したりしていません。

非クラスター化 ID シードを作成し、既存の非クラスター化インデックスをクラスター化インデックスに変更することについて、Portman に同意します。

クライアントで使用しているコンストラクトに関する限り...(データアダプター、データセット、データテーブルなど)。サーバー上のディスク io が 100% の場合、サーバーが現在処理できるよりも高速に見えるため、クライアント構成の分析に時間を費やすのが最善ではないと思います。

ポートマンの最小限のロギングに関するリンクをたどる場合、大量のコピーをトランザクションで囲むことはあまり役に立たないと思いますが、私は人生で何度も間違っていました;)

これは必ずしも今すぐ役立つとは限りませんが、現在の問題を把握している場合、この次のコメントは次のボトルネック (ネットワーク スループット) の解決に役立つ可能性があります - 特にインターネット経由の場合...

Chopeen も興味深い質問をしました。挿入に 300 レコード カウントのチャンクを使用することをどのように決定しましたか? SQL Server には既定のパケット サイズ (4096 バイトだと思います) があり、レコードのサイズを導き出し、クライアントとサーバーの間で送信されるパケットを効率的に使用していることを確認することは理にかなっています。(すべてのサーバー通信でパケット サイズを明らかに変更するサーバー オプションとは対照的に、クライアント コードでパケット サイズを変更できることに注意してください。おそらく良い考えではありません。) たとえば、レコード サイズが 300 レコード バッチで 4500 を必要とする場合バイトの場合、2 つのパケットを送信し、2 番目のパケットはほとんど無駄になります。バッチ レコード カウントが任意に割り当てられた場合、手早く簡単な計算を行うことは理にかなっているかもしれません。

私が知ることができる (そしてデータ型のサイズについて覚えている) ことから、各レコードには正確に 20 バイトがあります (int=4 バイトおよび smallint=2 バイトの場合)。300 レコード カウントのバッチを使用している場合、300 x 20 = 6,000 バイトを送信しようとしています (さらに、接続などのオーバーヘッドが少しあると推測しています)。これらを 200 レコード カウント バッチ (200 x 20 = 4,000 + オーバーヘッドの余地) = 1 パケットで送信する方が効率的かもしれません。繰り返しになりますが、ボトルネックはサーバーのディスク io にあるようです。

同じハードウェア/構成で生データ転送を SqlBulkCopy と比較していることは承知していますが、課題が私のものである場合にも、ここに行きます。

この投稿はかなり古いため、おそらくもう役に立たないでしょうが、次に、ディスクの RAID 構成と、使用しているディスクの速度をお尋ねします。データ ファイルに RAID 5 (理想的には 1) を使用する RAID 10 を使用するドライブにログ ファイルを配置してみてください。これにより、ディスク上のさまざまなセクターへの多くのスピンドルの動きを減らすことができ、非生産的な「移動」状態ではなく、読み取り/書き込みにより多くの時間を費やすことができます。データ ファイルとログ ファイルを既に分離している場合は、データ ファイルとは別の物理ディスク ドライブにインデックスを配置していますか (これはクラスター化インデックスでのみ可能です)。これにより、ログ情報の更新とデータの挿入を同時に実行できるようになるだけでなく、インデックスの挿入 (およびコストのかかるインデックス ページ操作) も同時に実行できるようになります。

于 2008-10-28T03:48:39.583 に答える
1

これはSSISパッケージを使用して実行できるように思えます。これらはSQL2000のDTSパッケージに似ています。私はそれらを使用して、プレーンテキストのCSVファイル、既存のSQLテーブル、さらには複数のワークシートにまたがる6桁の行を持つXLSファイルからすべてを正常に変換しました。C#を使用してデータをインポート可能な形式(CSV、XLSなど)に変換してから、SQLサーバーにスケジュールされたSSISジョブを実行させてデータをインポートすることができます。

SSISパッケージの作成は非常に簡単で、SQLServerのEnterpriseManagerツールに組み込まれているウィザード(「データのインポート」というラベルが付いていると思います)があり、ウィザードの最後にSSISパッケージとして保存するオプションがあります。Technetにもたくさんの情報があります。

于 2008-08-23T15:40:00.197 に答える
0

はい、あなたのアイデアが役立ちます。
ロード中に読み取りが行われない場合は、オプション 1 を使用してください。
処理中に宛先テーブルがクエリされている場合は、オプション 2 に頼ってください。

@アンドリューの
質問。300 のチャンクで挿入します。挿入する合計量はいくらですか? SQL サーバーは、300 個の単純な古い挿入を非常に高速に処理できるはずです。

于 2008-08-23T12:56:59.887 に答える