web-dev-qa-db-ja.com

セットベースのアルゴリズム/ UDFを実装する方法

800K行38列のテーブルのすべての行に対して実行する必要があるアルゴリズムがあります。このアルゴリズムはVBAに実装されており、他の列を操作するためにいくつかの列の値を使用して一連の計算を行います。

現在Excel(ADO)を使用してSQLをクエリし、クライアント側カーソルでVBAを使用して、すべての行をループするアルゴリズムを適用しています。動作しますが、実行に7時間かかります。

VBAコードは、T-SQLに再コード化するのに多くの作業が必要になるほど複雑です。

可能なルートとしてのCLR統合とUDFについて読みました。 VBAコードをSSISスクリプトタスクに入れてデータベースに近づけることも検討しましたが、この種のパフォーマンスの問題に対するエキスパートの方法論は確かに存在します。

理想的には、並列セットベースの方法で、できるだけ多くの行(すべて?)に対してアルゴリズムを実行できます。

このタイプの問題で最高のパフォーマンスを得る方法を大いに前提としたヘルプ。

-編集

コメントをありがとう、私はMS SQL 2014 Enterpriseを使用しています。詳細は次のとおりです。

アルゴリズムは、時系列データの特徴的なパターンを見つけます。アルゴリズム内の関数は、多項式平滑化、ウィンドウ処理を実行し、入力基準に基づいて関心領域を見つけて、ダース値といくつかのブール結果を返します。

私の質問は、実際のアルゴリズムよりも方法論についてです。一度に多くの行で並列計算を実現したい場合、私の選択肢は何ですか。

T-SQLへの再コード化をお勧めします。これは多くの作業ですが可能ですが、アルゴリズム開発者はVBAで作業し、頻繁に変更されるため、T-SQLバージョンと同期を保ち、すべてを再検証する必要があります。変化する。

T-SQLは、セットベースの関数を実装する唯一の方法ですか?

13
medwar19

方法論に関しては、あなたは間違ったbツリーを始めていると思います;-)。

知っていること:

最初に、状況について私たちが知っていることを統合して確認しましょう。

  • やや複雑な計算を実行する必要があります:
    • これは、このテーブルのすべての行で発生する必要があります。
    • アルゴリズムは頻繁に変更されます。
    • アルゴリズム...一部の列の値を使用して、他の列を操作します
    • 現在の処理時間は次のとおりです7時間
  • テーブル:
    • 800,000行が含まれています。
    • 38列あります。
  • アプリケーションのバックエンド:
  • データベースはSQL Server 2014、Enterprise Editionです。
  • すべての行に対して呼び出されるストアドプロシージャがあります。

    • これを実行するには50ミリ秒(平均では、私は想定しています)かかります。
    • 約4000行を返します。
    • 定義(少なくとも一部)は次のとおりです。

      SELECT AVG([AD_Sensor_Data])
                 OVER (ORDER BY [RowID] ROWS BETWEEN 5 PRECEDING AND 5 FOLLOWING)
                 as 'AD_Sensor_Data'
      FROM   [AD_Points]
      WHERE  [FileID] = @FileID
      ORDER BY [RowID] ASC
      

推測できること:

次に、これらすべてのデータポイントを一緒に見て、1つ以上のボトルネックを見つけるのに役立つ追加の詳細を合成できるかどうかを確認し、解決策を指すか、少なくともいくつかの可能な解決策を除外できます。

コメントでの現在の考え方は、SQL ServerとExcel間のデータ転送が主要な問題であることです。それは本当ですか?ストアドプロシージャが800,000行ごとに呼び出され、呼び出しごとに(つまり、各行ごとに)50ミリ秒かかる場合、合計で40,000秒(ミリ秒ではない)になります。これは666分(hhmm ;-)、つまり11時間強に相当します。しかし、プロセス全体の実行には7時間しかかからないと言われています。合計時間はすでに4時間です。計算を実行したり、結果をSQL Serverに保存したりするために時間を追加することもできます。だから何かがここでは正しくありません。

ストアドプロシージャの定義を見ると、@FileIDの入力パラメーターしかありません。 @RowIDにはフィルタはありません。したがって、次の2つのシナリオのいずれかが発生していると思います。

  • このストアドプロシージャはnotを実際に各行ごとに呼び出しますが、代わりに各@FileIDごとに呼び出されます。これは約4000行にわたるようです。返された前述の4000行がかなり一貫している場合、800,000行にグループ化されているのは200行のみです。そして、それぞれ50ミリ秒かかる200回の実行は、その7時間のうちわずか10秒です。
  • このストアドプロシージャが実際にすべての行に対して呼び出された場合、新しい@FileIDが初めて渡されたときに、新しい行がバッファプールにプルされるまでに少し時間がかかりますが、通常、次の3999の実行は返されます。すでにキャッシュされているため、より高速ですよね?

この「フィルター」ストアドプロシージャ、またはSQL ServerからExcelへのデータ転送に焦点を当てることは red herring だと思います。

現時点では、パフォーマンスの低下の最も関連する指標は次のとおりです。

  • 800,000行あります
  • 操作は一度に1行で機能します
  • データはSQL Serverに保存されているため、「[一部の列の値を使用する他の列を操作する [my em phas is ;-)]

私はそれを疑っています:

  • データの取得と計算には改善の余地がありますが、それらを改善しても、処理時間を大幅に短縮することにはなりません。
  • 主要なボトルネックは、800,000の個別のUPDATEステートメントを発行することです。これは、800,000の個別のトランザクションです。

私の推薦(現在入手可能な情報に基づく):

  1. 改善の最大の領域は、一度に(つまり、1つのトランザクションで)複数の行を更新することです。各FileIDではなく、各RowIDで機能するようにプロセスを更新する必要があります。そう:

    1. 特定のFileIDの4000行すべてを配列に読み込みます
    2. 配列には、操作されるフィールドを表す要素が含まれている必要があります
    3. 配列を循環し、現在のように各行を処理します
    4. 配列内のすべての行(つまり、この特定のFileID)が計算されると、次のようになります:
      1. トランザクションを開始する
      2. 各更新をRowIDごとに呼び出す
      3. エラーがなければ、トランザクションをコミットします
      4. エラーが発生した場合は、ロールバックして適切に処理します
  2. クラスター化インデックスが(FileID, RowID)としてまだ定義されていない場合は、それを考慮する必要があります(質問のコメントで@MikaelErikssonが提案したとおり)。これらのシングルトンUPDATEは役立ちませんが、すべてのFileIDに基づいているため、「フィルター」ストアドプロシージャで実行している操作など、集計操作が少なくともわずかに改善されます。

  3. ロジックをコンパイル済み言語に移動することを検討する必要があります。 .NET WinFormsアプリまたはコンソールアプリを作成することをお勧めします。 SQLエージェントまたはWindowsのスケジュールされたタスクを介して簡単にスケジュールできるので、コンソールアプリを好みます。 VB.NETとC#のどちらで行われるかは問題ではありません。 VB.NETは開発者にとってより自然な適合かもしれませんが、学習曲線はまだあります。

    現時点では、SQLCLRに移行する理由はありません。アルゴリズムが頻繁に変更されると、アセンブリを常に再配置する必要があり、煩わしくなります。コンソールアプリを再構築し、.exeをネットワーク上の適切な共有フォルダーに配置することで、同じプログラムを実行するだけで常に最新の状態になり、非常に簡単に実行できるようになります。

    問題が疑わしいものであり、一度に1つのUPDATEを実行しているだけの場合、処理をT-SQLに完全に移動しても効果があるとは思いません。

  4. 処理が.NETに移された場合、テーブル値パラメーター(TVP)を利用して、配列をストアドプロシージャに渡し、UPDATEを呼び出してTVPテーブル変数に結合することができます。したがって、単一のトランザクションです。 TVPは、単一のトランザクションにグループ化された4000 INSERTsを実行するよりも高速である必要があります。しかし、1つのトランザクションで4000 INSERTsを超えるTVPを使用することによる利益は、800,000の個別のトランザクションからそれぞれ4000行の200のトランザクションのみに移行した場合に見られる改善ほど大きくはないでしょう。

    TVPオプションはVBA側ではネイティブに利用できませんが、誰かがテストに値するかもしれない回避策を考え出しました:

    VBAからSQL Server 2008 R2に移行するときにデータベースのパフォーマンスを向上させるにはどうすればよいですか?

  5. フィルタープロシージャがFileID句でWHEREのみを使用しており、そのプロシージャが実際にすべての行ごとに呼び出されている場合、最初の実行の結果をキャッシュすることで、処理時間を節約できます。そして、それらの行ごとの残りの行にそれらを使用しますFileID、そうですか?

  6. 処理が完了したら、-FileIDthenで並列処理について説明します。しかし、それはその時点では必要ないかもしれません:)。 Excel、VBA、800kトランザクションの3つのかなり非理想的な部分を扱っているとすれば、SSISや平行四辺形の話、または誰が何を知っているかは、時期尚早な最適化/馬の前のカートタイプのものです。 。この7時間のプロセスを10分以下に短縮できるとしたら、それをさらに速くするための追加の方法を考えていますか?あなたが考えている目標完了時間はありますか?処理がファイルIDごとベースで行われると、VB.NETコンソールアプリケーション(つまり、コマンドライン.EXE)がある場合、いくつかの実行を妨げるものは何もないことに注意してください。これらのFileIDは一度に:)、SQLエージェントのCmdExecステップまたはWindowsのスケジュールされたタスクなどを介して.

また、いつでも「段階的」アプローチを取り、一度にいくつかの改善を行うことができます。たとえば、FileIDごとに更新を行うことから始めて、そのグループに対して1つのトランザクションを使用します。次に、TVPを機能させることができるかどうかを確認します。次に、そのコードを取得してVB.NETに移動する方法を確認します(TVPは.NETで機能するため、適切に移植できます)。


まだ役に立たないとわかっていること:

  • "フィルター"ストアドプロシージャはRowIDごとまたはFileIDで実行されますか?そのストアドプロシージャの完全な定義さえありますか?
  • テーブルの完全なスキーマ。このテーブルの幅はどれくらいですか?可変長フィールドはいくつありますか? NULL可能なフィールドはいくつありますか? NULL可能である場合、NULLを含むものはいくつですか?
  • このテーブルのインデックス。分割されていますか? ROWまたはPAGE圧縮が使用されていますか?
  • このテーブルはMB/GBでどれくらい大きいですか?
  • このテーブルのインデックスメンテナンスはどのように処理されますか?インデックスはどのように断片化されていますか?統計はどのように更新されていますか?
  • この7時間のプロセスの実行中に、他のプロセスがこのテーブルに書き込みますか?競合の可能性のある原因。
  • この7時間のプロセスの実行中に、他のプロセスがこのテーブルから読み取りますか?競合の可能性のある原因。

更新1:

** どのVBA(Visual Basic for Applications)とそれを使って何ができるかについて混乱があるようですが、これは私たち全員が同じWebページにいることを確認するためのものです。


更新2:

考慮すべきもう1つのポイント:接続はどのように処理されますか? VBAコードは、各操作ごとに接続を開いたり閉じたりしていますか?それとも、プロセスの開始時に接続を開き、プロセスの最後(つまり7時間後)に接続を閉じますか?接続プール(デフォルトではADOで有効にする必要があります)があっても、800,200回または1,600,000回の開閉ではなく、1回の開閉の間にかなりの影響があります。これらの値は、少なくとも800,000の更新と200または800kのEXECに基づいています(フィルターストアドプロシージャが実際に実行される頻度によって異なります)。

接続が多すぎるというこの問題は、上で概説した推奨事項によって自動的に緩和されます。トランザクションを作成し、そのトランザクション内ですべてのUPDATEを実行することにより、その接続を開いたままにして、UPDATEごとにそれを再利用します。指定されたFileIDごとに4000行を取得するための最初の呼び出しから接続を開いたままにするか、その「取得」操作の後で閉じて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倍以上に)短縮できることが保証されています。

8
Solomon Rutzky

私見で、VBAサブをSQLに再コード化することは不可能であるという前提から作業していますが、VBAスクリプトがExcelファイルで評価を完了し、結果をSSIS経由でSQLサーバーに書き込むことを許可することを検討しましたか?

VBAサブの開始と終了をファイルシステムオブジェクトまたはサーバーのインジケーターの反転で終了し(サーバーに書き戻すように接続を既に構成している場合)、SSIS式を使用してこのインジケーターを確認します。 SSISソリューション内の特定のタスクのdisableプロパティ(そのため、スケジュールの超過を心配している場合、インポートプロセスはVBAサブが完了するまで待機します)。

さらに、VBAスクリプトをプログラムで開始させることもできます(少し不安定ですが、workbook_open()プロパティを使用して、過去にこの種の「起動して忘れる」タスクをトリガーしました)。

VBスクリプトの評価時間が問題になり始めたら、VB開発者が彼のコードをVBスクリプトに移植できるかどうかを確認できますSSISソリューション内のタスク-私の経験では、Excelアプリケーションは、このボリュームでデータを操作するときに多くのオーバーヘッドを引き出します。

2
Peter Vandivier