web-dev-qa-db-ja.com

ストアドプロシージャを並列で実行する

同じストアドプロシージャを、異なるパラメーターを使用して複数回同時に実行しようとしています。

SQL 2014を使用しています

これは、手順が完了するまでに約7時間かかるためです。実際には同じプロセスを何度も実行します。したがって、たとえば、ブランチごとに新しいデータベースとテーブルを構築できます。

私がやりたいのは、ストアドプロシージャを分解して、ブランチごとに実行し、各クエリを並行して実行できるようにすることです。私はこれを個別のクエリウィンドウで実行することでテストしましたが、80%ほど速く実行されました。

誰かが私にクエリを並行して実行するためのダミーガイドを教えてもらえますか?

9
user124569

ある時点で、私は StackOverflowでこの質問に回答しました ですが、DBA.SEでもその情報を修正し、更新しておくと便利だと思われます。

完全に明示的に:TSQLはしない(それ自体)他のTSQL操作を非同期で起動する機能があります

それはあなたがまだたくさんの選択肢がないことを意味しません(それらのいくつかは他の答えで言及されています):

  • SQLエージェントジョブ:複数のSQLジョブを作成し、必要なときに実行するようにスケジュールするか、「マスターコントロール」ストアドプロシージャから非同期に開始しますsp_start_jobを使用します。プログラムで進捗状況を監視する必要がある場合は、ジョブがそれぞれカスタムJOB_PROGRESSテーブルを更新することを確認してください(または、ドキュメントに記載されていない関数xp_sqlagent_enum_jobsを使用して、ジョブがまだ終了しているかどうかを確認できます この優れた記事 (Gregory A. Larsenによる)。パラメータが異なる同じストアドプロシージャを実行している場合でも、並列プロセスを実行するのと同じ数の個別のジョブを作成する必要があります。
  • SSISパッケージ:単純な分岐タスクフローでSSISパッケージを作成します。 SSISはこれらのタスクを個別のspidで起動し、SQLは並列で実行します。
  • カスタムアプリケーション:選択した言語(C#、Powershellなど)で、その言語が提供する非同期メソッドを使用して簡単なカスタムアプリを記述します。各アプリケーションスレッドでSQLストアドプロシージャを呼び出します。
  • OLEオートメーション:SQLでは、sp_oacreateおよびsp_oamethodを使用して、他のストアドプロシージャを呼び出す新しいプロセスを起動します- この記事 、同じくGregory A. Larsen。
  • Service BrokerService Brokerこの記事の非同期実行の良い例 の使用を検討してください。 。
  • CLR並列実行この記事 で説明されているように、CLRコマンドParallel_AddSqlおよびParallel_Executeを使用しますAlan Kaplan(SQL2005 +のみ)。
  • スケジュールされたWindowsタスク:完全を期すためにリストされていますが、私はこのオプションのファンではありません。

私の場合、おそらく、より単純なシナリオでは複数のSQLエージェントジョブを使用し、より複雑なシナリオではSSISパッケージを使用します。

あなたの場合、200の個別のスレッドを起動しようとしない限り、スケジュールされた複数のエージェントジョブは、単純で管理しやすい選択のように聞こえます。

1つの最後のコメント:SQLは、可能な場合は常に個々の操作を並列化しようとしています*。つまり、2つのタスクを連続して実行するのではなく、同時に実行しても、すぐに終了する保証はありません。それが実際に何か改善するかどうかを確認するために注意深くテストしてください。

DTSパッケージを同時に作成して8つのタスクを実行する開発者がいました。残念ながら、これは4 CPUサーバーのみでした:)

*デフォルト設定を想定しています。これは、サーバーの最大並列度またはアフィニティマスクを変更するか、MAXDOPクエリヒントを使用して変更できます。

8
BradC

最善の策は、同じスケジュールで3つの別々のジョブを作成して、ジョブを同時に開始することです。ジョブの実行内容に応じて、ブロッキングとデッドロックを注意深く監視する必要があります。

別のオプションは、SPを並列に呼び出すためのNのオペレーターを含むSSISパッケージを作成することです

2
Pixelated

Powershellを使用できます。 SQL Serverを使用していると仮定すると、次のようなことができます:(今テストされ、クリーンアップされています)

#This script creates a number of connections (one per entry in $Commands) 
# to a SQL Server instance ($Server) and database ($DBName)
#Driver variables


#Set Initial collections and objects    
$Server= "(local)\sql2016cs" ; #Server to connect to
$DBName = "Test" ; #Database to connect to

$Commands = @()
$Commands += "EXEC sp_LogMe 'a'"
$Commands += "EXEC sp_LogMe 'b'"

#Loop through commands array, create script block for establishing SMO connection/query
#Start-Job for each script block
foreach ($sql in $Commands ) {

# All of that extra information after "Smo" tells it to load just v12 (for when you have multiple
#   versions of SQL installed.)  Note: V13 is 2016.
 $cmdstr =@"
`Add-Type -AssemblyName "Microsoft.SqlServer.Smo,Version=$(13).0.0.0,Culture=neutral,PublicKeyToken=89845dcd8080cc91"
`[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
`$SqlConn = New-Object Microsoft.SqlServer.Management.Smo.Server ("$Server")
`$SqlConn.Databases["$DBName"].ExecuteNonQuery("$sql")
"@

#Uncomment the next like to print the command string for debugging
# $cmdstr
#Execute script block in jobs to run the command asyncronously
$cmd = [ScriptBlock]::Create($cmdstr)
Start-Job -ScriptBlock $cmd
}

注:テストしたテスト済みの類似のものからこれを取得しました: https://sqlstudies.com/2016/02/24/powershell-script-to-create-multiple-sql-server-connections/

その1つでは、ループを実行して、同じことを実行する一連のコマンドを作成していました。このスクリプトは、スクリプトブロックを使用して各コマンドを非同期で実行しますが、実際のコマンドは異なります。物事を簡単にするために、実行したいコマンドのリストを配列に入れ、配列をループしました。

2
Kenneth Fisher

マルチスレッドでC#アプリを使用していますParallel.ForEachは、さまざまなパラメーターでspを呼び出します。 3つのセクションがあります。 Init、Body、localFinally

public void NearLinkParallelGeneration(avl_range avl_pending, DateTime dt_start_process)
    {
        var parallelOptions = new ParallelOptions
        {
            MaxDegreeOfParallelism = Environment.ProcessorCount + 2
        };

        // create the partition based on the input
        var partitions = Partitioner
                            .Create(
                                fromInclusive: avl_pending.begin,
                                toExclusive: avl_pending.end,
                                rangeSize: 100
                            )
                            .GetDynamicPartitions();

        Parallel.ForEach(
            source: partitions,
            parallelOptions: parallelOptions,
            localInit: () =>
            {
                NpgsqlConnection conn = new NpgsqlConnection(strConnection);
                NpgsqlCommand cmd = new NpgsqlCommand();
                try
                {
                    conn.Open();
                    cmd.Connection = conn;
                    cmd.CommandText = "SELECT * FROM avl_db.process_near_link(@begin, @end, @start_time);";
                    cmd.CommandType = CommandType.Text;

                    NpgsqlParameter p = new NpgsqlParameter("@begin", NpgsqlDbType.Bigint);
                    cmd.Parameters.Add(p);

                    p = new NpgsqlParameter("@end", NpgsqlDbType.Bigint);
                    cmd.Parameters.Add(p);

                    p = new NpgsqlParameter("@start_time", NpgsqlDbType.Timestamp);
                    p.Value = dt_start_process;
                    cmd.Parameters.Add(p);
                }
                catch (NpgsqlException ex)
                {
                    Console.WriteLine(ex.InnerException);
                }
                catch (System.Exception ex)
                {
                    Console.WriteLine(ex.InnerException);
                }

                return new { Connection = conn, Command = cmd };
            },
            body: (source, state, local) =>
            {
                if (local.Connection.State == ConnectionState.Open)
                {
                    string strResult = String.Format("From: {0} - To: {1}", source.Item1, source.Item2);
                    Console.WriteLine(strResult);

                    try
                    {
                        local.Command.Parameters["@begin"].Value = source.Item1;
                        local.Command.Parameters["@end"].Value = source.Item2;
                        local.Command.ExecuteNonQuery();
                    }
                    catch (NpgsqlException ex)
                    {
                        Console.WriteLine(ex.InnerException);
                    }
                    catch (System.Exception ex)
                    {
                        Console.WriteLine(ex.InnerException);
                    }

                    //strResult = String.Format("DONE From: {0} - To: {1}", source.Item1, source.Item2);
                    //Console.WriteLine(strResult);

                }
                return local;
            },
            localFinally: local =>
            {
                local.Command?.Dispose();
                local.Connection?.Dispose();
            }
        );
    }
1

ForEach -Parallel Powershell。

以下の例(私の質問 Powershell Run Stored Procedures in Parallel in Database )から取得)は、データベース内のすべてのストアドプロシージャを実行します。

Workflow TestRunParallelExecute
{
    $ServerName = "localhost"
    $DatabaseName = "testrun"
    $Procedure_Query = "select name from sys.procedures"
    $Procedure_List = (Invoke-Sqlcmd -Server $ServerName -Database $DatabaseName -Query $Procedure_Query)

    ForEach -Parallel ($Procedure in $Procedure_List.Name)
    {
         Invoke-Sqlcmd -Server $ServerName -Database $DatabaseName -Query $Procedure 
    }
}
TestRunParallelExecute
cls
1
user162241

これは私が職場で使用したユースケースを思い出させるので、それをどのように解決するかを説明します。

最初にすでに述べたように、SQLにはUnixの「ノーハウ」のようなものは存在しないと思います。1つの接続= 1つのステートメントで、すべて(ロック、コミット、エラー...)

無料のETL Talendを使用してDBに接続するように構成し、ストアドプロシージャをラップする一連の並列ジョブを実行する方法を見つけます。

Iterateコンポーネントを使用し、必要なだけループして、multi-threadsオプション。

0
Blag