方法論に関しては、間違ったBツリーを作成していると思います;-)。
私たちが知っていること:
まず、状況について知っていることを統合して確認しましょう。
推測できること:
次に、これらのすべてのデータポイントを一緒に調べて、1つ以上のボトルネックを見つけるのに役立つ追加の詳細を合成し、解決策を示すか、少なくともいくつかの可能な解決策を除外できるかどうかを確認します。
コメントでの現在の考え方は、主な問題はSQL ServerとExcel間のデータ転送であるということです。それは本当ですか?ストアドプロシージャが800,000行ごとに呼び出され、各呼び出しごとに(つまり、各行ごとに)50ミリ秒かかると、合計で40,000秒(ミリ秒ではない)になります。そして、これは666分(hhmm ;-)に相当するか、11時間強です。しかし、プロセス全体の実行にはわずか7時間しかかからないと言われていました。合計時間はすでに4時間であり、計算を行ったり、結果をSQL Serverに保存したりするための時間を追加しました。そのため、ここには何かがありません。
ストアドプロシージャの定義を見ると、の入力パラメーターのみがあります@FileID
。にはフィルタがありません@RowID
。したがって、次の2つのシナリオのいずれかが発生していると思われます。
- このストアドプロシージャは実際には各行ごとに呼び出されるのではなく、各行ごとに呼び出され、
@FileID
約4000行にまたがっているように見えます。指定された4000行がかなり一貫した量である場合、800,000行にグループ化されているのは200のみです。また、50ミリ秒かかる200回の実行は、その7時間のうちわずか10秒に相当します。
- このストアドプロシージャが実際にすべての行に対して呼び出される場合、新しいもの
@FileID
が最初に渡されるとき、新しい行をバッファープールにプルするのに少し長くかかりませんが、次の3999の実行は通常、既に存在しているために速く戻りますキャッシュされていますか?
この「フィルター」ストアドプロシージャ、またはSQL ServerからExcelへのデータ転送に焦点を当てるのは赤いニシンだと思います。
現時点では、パフォーマンスの低さの最も関連性の高い指標は次のとおりです。
- 800,000行あります
- 操作は一度に1行で動作します
- データはSQL Serverに保存されるため、「[一部の列の値を使用して他の列を操作する」」[私のem phasは;-)]
私はそれを疑います:
- データの取得と計算には改善の余地がありますが、それらを改善しても処理時間の大幅な短縮にはなりません。
- 主要なボトルネックは、800,000の個別の
UPDATE
ステートメント、つまり800,000の個別のトランザクションを発行することです。
私の推奨事項(現在入手可能な情報に基づく):
改善の最大の領域は、一度に(つまり、1つのトランザクションで)複数の行を更新することです。それぞれのFileID
代わりにそれぞれの観点で動作するようにプロセスを更新する必要がありますRowID
。そう:
- 特定の4000行すべてを
FileID
配列に読み込む
- 配列には、操作されるフィールドを表す要素が含まれている必要があります
- 配列を循環し、現在のように各行を処理します
- 配列内のすべての行(つまり、この特定の行
FileID
)が計算されたら:
- トランザクションを開始する
- 各更新ごとに呼び出す
RowID
- エラーがなければ、トランザクションをコミットします
- エラーが発生した場合、ロールバックして適切に処理する
クラスター化インデックスがまだ定義されていない(FileID, RowID)
場合は、(質問のコメントで@MikaelErikssonが提案したように)それを考慮する必要があります。これらのシングルトンUPDATEには役立ちませんが、すべてがに基づいているため、その「フィルター」ストアドプロシージャで実行していることなど、集約操作を少なくともわずかに改善しFileID
ます。
ロジックをコンパイル済み言語に移行することを検討する必要があります。.NET WinFormsアプリまたはコンソールアプリを作成することをお勧めします。SQL AgentまたはWindowsのスケジュールされたタスクを介して簡単にスケジュールできるので、コンソールアプリが好きです。VB.NETまたはC#で実行されるかどうかは関係ありません。VB.NETは開発者にとってより自然なフィットかもしれませんが、まだある程度の学習曲線があります。
現時点では、SQLCLRに移行する理由はありません。アルゴリズムが頻繁に変更されると、アセンブリを常に再展開するのは面倒です。コンソールアプリケーションを再構築し、.exeをネットワーク上の適切な共有フォルダーに配置して、同じプログラムを実行するだけで、常に最新の状態になるようにするのは、かなり簡単です。
問題が疑わしいもので、一度に1つのUPDATEだけを実行している場合、処理をT-SQLに完全に移動しても役立つとは思いません。
処理が.NETに移動した場合、UPDATE
TVPテーブル変数にそのJOINを呼び出すストアドプロシージャに配列を渡すように、テーブル値パラメーター(TVP)を使用できます。。TVPはINSERT
、単一のトランザクションにグループ化された4000を実行するよりも高速でなければなりません。ただし、INSERT
1トランザクションで4000 秒を超えるTVPを使用することによる利益は、800,000の個別トランザクションから各4000行の200トランザクションのみに移行する場合に見られる改善ほど大きくはないでしょう。
TVPオプションはVBA側ではネイティブに利用できませんが、誰かがテストする価値がある回避策を思い付きました。
VBAからSQL Server 2008 R2に移行するときにデータベースのパフォーマンスを改善するにはどうすればよいですか?
フィルタprocがのみ使用している場合FileID
にはWHERE
句、及びそのprocは本当にすべての行ごとに呼び出されている場合は、最初の実行の結果をキャッシュし、そのあたりの行の残りのためにそれらを使用することによって、いくつかの処理時間を節約することができFileID
、正しい?
あなたは、処理を成し遂げるたらFILEIDごとに、そして我々は、並列処理の話を始めることができます。しかし、それはその時点では必要ではないかもしれません:)。Excel、VBA、800kトランザクション、SSISの話、平行四辺形、または誰が何を知っているのが時期尚早の最適化/馬の前に来るタイプのものの3つのかなり理想的でない非理想的な部分を扱っていることを考えると。この7時間のプロセスを10分以下に短縮できたとしても、さらに高速化するための追加の方法を考えていますか?あなたが念頭に置いている目標完了時間はありますか?処理がFileIDごとに行われることに注意してください 基本的に、VB.NETコンソールアプリ(つまり、コマンドライン.EXE)があれば、SQL Agent CmdExecステップまたはWindowsスケジュールタスクを介して、一度にそれらのFileIDのいくつかを実行することを妨げるものは何もありません。等
また、いつでも「段階的な」アプローチを取り、一度にいくつかの改善を行うことができます。更新を実行することから始めて、FileID
そのグループに対して1つのトランザクションを使用するなど。次に、TVPが機能するかどうかを確認します。次に、そのコードを取得してVB.NETに移動する方法を確認します(TVPは.NETで機能するため、うまく移植できます)。
私たちが知らないことはまだ助けになるかもしれません:
- 「フィルター」ストアドプロシージャはRowIDまたはFileIDごとに実行されますか?そのストアドプロシージャの完全な定義さえありますか?
- テーブルの完全なスキーマ。このテーブルの幅は?可変長フィールドはいくつありますか?NULL可能フィールドはいくつありますか?NULL可能なものがある場合、NULLを含むものはいくつですか?
- このテーブルのインデックス。パーティション化されていますか?行またはページの圧縮が使用されていますか?
- この表のMB / GBの大きさはどれくらいですか?
- このテーブルのインデックスメンテナンスはどのように処理されますか?インデックスはどの程度断片化されていますか?統計は現在までどのように更新されていますか?
- この7時間のプロセスが行われている間に、他のプロセスはこのテーブルに書き込みますか?競合の可能性のあるソース。
- この7時間のプロセスの実行中に、他のプロセスがこのテーブルから読み取りますか?競合の可能性のあるソース。
更新1:
** VBA(Visual Basic for Applications)とそれを使って何ができるかについて混乱が生じているようです。そのため、これは単に同じWebページにいることを確認するためです。
更新2:
考慮すべきもう1つのポイント:接続はどのように処理されますか?VBAコードは、各操作ごとに接続を開いたり閉じたりしますか、またはプロセスの開始時に接続を開き、プロセスの終了時に(つまり、7時間後)接続を閉じますか?接続プール(デフォルトではADOを有効にする必要があります)でも、800、200または1,600,000回の開閉とは対照的に、1回の開閉の間に大きな影響があります。これらの値は、少なくとも800,000個のUPDATEと200個または800k個のEXECに基づいています(フィルターストアドプロシージャが実際に実行される頻度に依存します)。
接続が多すぎるというこの問題は、上記の推奨事項によって自動的に軽減されます。トランザクションを作成し、そのトランザクション内ですべてのUPDATEを実行することにより、その接続を開いたままにして、それぞれに対して再利用することになりますUPDATE
。指定されたごとに4000行を取得するための最初の呼び出しから接続を開いたままFileID
にするか、その「取得」操作後に閉じてUPDATEのために再び開くかは、どちらかの違いについて話しているため、それほど影響はありませんプロセス全体で合計200または400の接続。
更新3:
簡単なテストをいくつか行いました。これはかなり小規模なテストであり、まったく同じ操作ではないことに注意してください(純粋なINSERT対EXEC + UPDATE)。ただし、接続とトランザクションの処理方法に関連するタイミングの違いは依然として関連しているため、情報はここで比較的類似した影響を持つと推定できます。
テストパラメータ:
- SQL Server 2012 Developer Edition(64ビット)、SP2
テーブル:
CREATE TABLE dbo.ManyInserts
(
RowID INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
InsertTime DATETIME NOT NULL DEFAULT (GETDATE()),
SomeValue BIGINT NULL
);
操作:
INSERT INTO dbo.ManyInserts (SomeValue) VALUES ({LoopIndex * 12});
- 各テストあたりの合計挿入数:10,000
- 各テストごとのリセット:(
TRUNCATE TABLE dbo.ManyInserts;
このテストの性質上、FREEPROCCACHE、FREESYSTEMCACHE、およびDROPCLEANBUFFERSを実行しても、多くの価値はありませんでした。)
- 復旧モデル:シンプル(およびログファイルに1 GBの空き容量がある場合があります)
- トランザクションを使用するテストでは、トランザクションの数に関係なく、単一の接続のみを使用します。
結果:
Test Milliseconds
------- ------------
10k INSERTs across 10k Connections 3968 - 4163
10k INSERTs across 1 Connection 3466 - 3654
10k INSERTs across 1 Transaction 1074 - 1086
10k INSERTs across 10 Transactions 1095 - 1169
ご覧のとおり、DBへのADO接続がすべての操作で既に共有されている場合でも、明示的なトランザクション(ADOオブジェクトがこれを処理できるはずです)を使用してバッチにグループ化すると、大幅に(つまり2倍以上の改善)全体の処理時間を短縮します。