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


13

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

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

VBAコードは非常に複雑であるため、T-SQLに再コーディングするのは非常に手間がかかります。

可能なルートとしてのCLR統合とUDFについて読みました。また、データベースに近づけるためにSSISスクリプトタスクにVBAコードを配置することも考えましたが、このタイプのパフォーマンスの問題に対する専門的な方法論が存在することは確かです。

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

この種の問題で最高のパフォーマンスを得る方法に大きく依存するヘルプ。

-編集

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

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

私の質問は、実際のアルゴリズムよりも方法論に関するものです。一度に多くの行で並列計算を実現したい場合、私のオプションは何ですか。

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

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


3
SSISは、データフローを適切に設計すれば、ネイティブな並列化を提供できます。行計算によってこの行を実行する必要があるため、これが探しているタスクです。ただし、詳細(スキーマ、関連する計算、およびこれらの計算が何を達成することを望んでいるか)を提供できない限り、最適化を支援することは不可能です。彼らは、アセンブリで物事を書くと最速のコードになると言いますが、私のように恐ろしくそれを吸うと、それはまったく効率的ではなくなります
-billinkc

2
各行を個別に処理する場合、800K行をNバッチに分割しN、アルゴリズムのインスタンスをN別々のプロセッサ/コンピューターで実行できます。一方、主なボトルネックは何ですか-データをSQL ServerからExcelに転送するのか、実際の計算ですか?ダミーの結果をすぐに返すようにVBA関数を変更した場合、プロセス全体にどれくらい時間がかかりますか?それでも数時間かかる場合、ボトルネックはデータ転送にあります。数秒かかる場合は、計算を行うVBAコードを最適化する必要があります。
ウラジミールバラノフ

これは、ストアド・プロシージャとして呼び出されるフィルタです: 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 ではManagement Studioで行ごとに呼び出されるこの関数は50ミリ秒を取る
medwar19

1
そのため、50ミリ秒かかり、800000回(11時間)実行するクエリが時間のかかるものです。@FileIDは行ごとに一意ですか?または重複があるため、クエリを実行する必要がある回数を最小限に抑えることができますか?また、一度にすべてのファイルIDのローリング平均をステージングテーブルに事前計算し(FileIDのパーティションを使用)、各行のウィンドウ関数を使用せずにそのテーブルをクエリすることもできます。ステージングテーブルの最適なセットアップは、クラスタ化インデックスがオンになっている必要があるよう(FileID, RowID)です。
ミカエルエリクソン

1
何よりも、各行のデータベースに触れる必要性を何らかの形で取り除くことができれば最高です。つまり、TSQLに移動してローリングavgクエリに参加するか、行ごとに十分な情報を取得する必要があるため、複数の子行が含まれている場合は何らかの方法でエンコードされている可能性があります(xml) 。
ミカエルエリクソン

回答:


8

方法論に関しては、間違った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つのシナリオのいずれかが発生していると思われます。

  • このストアドプロシージャは実際には各行ごとに呼び出されるのではなく、各行ごとに呼び出され、@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. 改善の最大の領域は、一度に(つまり、1つのトランザクションで)複数の行を更新することです。それぞれのFileID代わりにそれぞれの観点で動作するようにプロセスを更新する必要がありますRowID。そう:

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

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

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

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

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

    TVPオプションはVBA側ではネイティブに利用できませんが、誰かがテストする価値がある回避策を思い付きました。

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

  5. フィルタprocがのみ使用している場合FileIDにはWHERE句、及びそのprocは本当にすべての行ごとに呼び出されている場合は、最初の実行の結果をキャッシュし、そのあたりの行の残りのためにそれらを使用することによって、いくつかの処理時間を節約することができFileID、正しい?

  6. あなたは、処理を成し遂げるたら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倍以上の改善)全体の処理時間を短縮します。


srutzkyが提案しているのは、優れた「ミドルマン」アプローチです。つまり、PowerShellを使用してSQL Serverから必要なデータを取得し、VBAスクリプトを呼び出してデータを処理し、SQL Serverで更新SPを呼び出します。 、キーと更新された値をSQLサーバーに返します。このようにして、セットベースのアプローチと既存のアプローチを組み合わせます。
スティーブMangiameli

@SteveMangiameliこんにちはスティーブ、コメントありがとう。私はもっ​​と早く答えたでしょうが、病気でした。あなたのアイデアが、私が提案しているものとどのくらい違うのか、私は興味があります。すべての兆候は、VBAを実行するためにExcelがまだ必要であることです。または、PowerShellがADOを置き換えることを提案していますか。I/ Oでより高速であれば、I / Oのみを置き換えるだけでも価値がありますか。
ソロモンラッツキー

1
心配いりません、気持ちが良くなりました。私はそれが良いだろうとは知りません。何がわからないのかわからないので、あなたはすばらしい分析をしましたが、まだいくつかの仮定をしなければなりません。I / Oは、それ自体で置き換えるのに十分なほど重要です。私たちは知りません。私はあなたが提案したものに役立つかもしれない別のアプローチを提示したかったです。
スティーブMangiameli

@SteveMangiameliありがとう。そして、それを明確にしてくれてありがとう。私はあなたの正確な方向について確信が持てなかったので、推測しないことが最善だと考えました。はい、どのような変更を加えることができるかについてどのような制約があるのか​​わからないので、オプションが多い方が良いことに同意します。
ソロモンラッツキー

ちょっとすごい、詳細な考えをありがとう!インデックスとクエリを最適化し、ボトルネックを見つけようとして、SQL側でテストを行ってきました。IOが行き詰まったため、36コア、1 TBのPCIe SSDを削除した適切なサーバーに投資しました。次に、並列実行のために複数のスレッドを開くように見えるSSISでVBコードを直接呼び出します。
medwar19

2

私見とVBAサブをSQLに再コーディングすることは不可能であるという仮定から作業していますが、VBAスクリプトがExcelファイルで評価を終了し、SSIS経由でSQLサーバーに結果を書き戻すことを許可することを検討しましたか?

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

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

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

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