web-dev-qa-db-ja.com

大量のSQLクエリを非同期およびスレッドで実行する方法

問題:大量のSQLクエリ(約10k〜20k)があり、それらを50(以上)のスレッドで非同期に実行したい。

このジョブ用にPowerShellスクリプトを記述しましたが、非常に低速です(すべての実行に約20時間かかりました)。 望ましい結果は最大3-4時間です

質問:このPowerShellスクリプトをどのように最適化できますか? pythonc#などの別のテクノロジーを再考して使用する必要がありますか?

whoisactiveで確認するとクエリが高速に実行されているため、これはPowerShellの問題だと思います。各スレッドに対して個別のPSインスタンスが作成されるため、ジョブの作成、終了、アンロードには多くの時間がかかります。

マイコード:

$NumberOfParallerThreads = 50;


$Arr_AllQueries = @('Exec [mystoredproc] @param1=1, @param2=2',
                    'Exec [mystoredproc] @param1=11, @param2=22',
                    'Exec [mystoredproc] @param1=111, @param2=222')

#Creating the batches
$counter = [pscustomobject] @{ Value = 0 };
$Batches_AllQueries = $Arr_AllQueries | Group-Object -Property { 
    [math]::Floor($counter.Value++ / $NumberOfParallerThreads) 
};

forEach ($item in $Batches_AllQueries) {
    $tmpBatch = $item.Group;

    $tmpBatch | % {

        $ScriptBlock = {
            # accept the loop variable across the job-context barrier
            param($query) 
            # Execute a command

            Try 
            {
                Write-Host "[processing '$query']"
                $objConnection = New-Object System.Data.SqlClient.SqlConnection;
                $objConnection.ConnectionString = 'Data Source=...';

                $ObjCmd = New-Object System.Data.SqlClient.SqlCommand;
                $ObjCmd.CommandText = $query;
                $ObjCmd.Connection = $objConnection;
                $ObjCmd.CommandTimeout = 0;

                $objAdapter = New-Object System.Data.SqlClient.SqlDataAdapter;
                $objAdapter.SelectCommand = $ObjCmd;
                $objDataTable = New-Object System.Data.DataTable;
                $objAdapter.Fill($objDataTable)  | Out-Null;

                $objConnection.Close();
                $objConnection = $null;
            } 
            Catch 
            { 
                $ErrorMessage = $_.Exception.Message
                $FailedItem = $_.Exception.ItemName
                Write-Host "[Error processing: $($query)]" -BackgroundColor Red;
                Write-Host $ErrorMessage 
            }

        }

        # pass the loop variable across the job-context barrier
        Start-Job $ScriptBlock -ArgumentList $_ | Out-Null
    }

    # Wait for all to complete
    While (Get-Job -State "Running") { Start-Sleep 2 }

    # Display output from all jobs
    Get-Job | Receive-Job | Out-Null

    # Cleanup
    Remove-Job *

}

[〜#〜]更新[〜#〜]

Resources: DBサーバーはリモートマシン上にあります。

  • 24GB RAM、
  • 8コア
  • 500GBストレージ、
  • SQL Server 2016

最大のCPUパワーを使用したいと考えています。

フレームワークの制限:唯一の制限はnottoですSQL Serverを使用してクエリを実行します。リクエストは、Powershell、C#、Pythonなどの外部ソースから送信されます。

18
Nyagolova

RunspacePoolはここに行く方法です、これを試してください:

$AllQueries = @( ... )
$MaxThreads = 5

# Each thread keeps its own connection but shares the query queue
$ScriptBlock = {
    Param($WorkQueue)

    $objConnection = New-Object System.Data.SqlClient.SqlConnection
    $objConnection.ConnectionString = 'Data Source=...'

    $objCmd = New-Object System.Data.SqlClient.SqlCommand
    $objCmd.Connection = $objConnection
    $objCmd.CommandTimeout = 0

    $query = ""

    while ($WorkQueue.TryDequeue([ref]$query)) {
        $objCmd.CommandText = $query
        $objAdapter = New-Object System.Data.SqlClient.SqlDataAdapter $objCmd
        $objDataTable = New-Object System.Data.DataTable
        $objAdapter.Fill($objDataTable) | Out-Null
    }

    $objConnection.Close()

}

# create a pool
$pool = [RunspaceFactory]::CreateRunspacePool(1, $MaxThreads)
$pool.ApartmentState  = 'STA'
$pool.Open()

# convert the query array into a concurrent queue
$workQueue = New-Object System.Collections.Concurrent.ConcurrentQueue[object]
$AllQueries | % { $workQueue.Enqueue($_) }

$threads = @()

# Create each powershell thread and add them to the pool
1..$MaxThreads | % {
    $ps = [powershell]::Create()
    $ps.RunspacePool = $pool
    $ps.AddScript($ScriptBlock) | Out-Null
    $ps.AddParameter('WorkQueue', $workQueue) | Out-Null
    $threads += [pscustomobject]@{
        Ps = $ps
        Handle = $null
    }
}

# Start all the threads
$threads | % { $_.Handle = $_.Ps.BeginInvoke() }

# Wait for all the threads to complete - errors will still set the IsCompleted flag
while ($threads | ? { !$_.Handle.IsCompleted }) {
    Start-Sleep -Seconds 1
}

# Get any results and display an errors
$threads | % {
    $_.Ps.EndInvoke($_.Handle) | Write-Output
    if ($_.Ps.HadErrors) {
        $_.Ps.Streams.Error.ReadAll() | Write-Error
    }
}

Powershellジョブとは異なり、RunspacePoolsはリソースを共有できます。したがって、すべてのクエリの1つの同時キューがあり、各スレッドはデータベースへの独自の接続を維持します。

他の人が言ったように-データベースのストレステストを行わない限り、クエリを一括挿入に再編成する方が良いでしょう。

5
antonyoni

各ワーカースレッドでデータベース接続を開いたままにして、そのスレッドによって実行されるすべてのクエリに使用できるように、スクリプトを再編成する必要があります。現在、クエリごとに新しいデータベース接続を開いているため、大量のオーバーヘッドが発生します。そのオーバーヘッドを排除することで、目標までまたはそれを超える速度が得られます。

5
Warren Dew
  1. テーブルとそのテーブルでの操作に基づいてクエリをグループ化します。これを使用して、さまざまなテーブルに対して実行できる非同期SQLクエリの量を特定できます。
  2. 実行する各テーブルのサイズを確認してください。テーブルに数百万の行が含まれていて、他のテーブルとの結合操作を行うと時間が長くなるため、またはCUD操作の場合は、テーブルもロックされる可能性があるためです。
    1. また、仮定ではなく、CPUコアに基づいてスレッド数を選択します。 CPUコアは一度に1つのプロセスを実行するので、コアの数* 2スレッドを効率的に作成できるためです。

したがって、最初にデータセットを調べ、次に上記の2つの項目を実行して、すべてのクエリが並列かつ効率的に実行されているものを簡単に識別できるようにします。

これがいくつかのアイデアを与えることを願っています。 pythonスクリプトを使用することをお勧めします。これにより、複数のプロセスを簡単にトリガーし、そのアクティビティを監視することもできます。

2
Hakuna Matata

悲しいことに、この瞬間にこれに完全に答える適切な時間はありませんが、これは役立つはずです:

最初に、CPU全体を使用して、ほとんど約束されたほど多くのレコードを挿入することはありません。だが!

表示されているので、SQL文字列コマンドを使用しています。

  1. 挿入をたとえば〜100-〜1000のグループに分割し、手動で一括挿入を作成します。

POCとしてのこのようなもの:

  $query = "INSERT INTO [dbo].[Attributes] ([Name],[PetName]) VALUES "

  for ($alot = 0; $alot -le 10; $alot++){
     for ($i = 65; $i -le 85; $i++) {
       $query += "('" + [char]$i + "', '" + [char]$i + "')"; 
       if ($i -ne 85 -or $alot -ne 10) {$query += ",";}
      }
   }

バッチが構築されたら、既存のコードを効果的に使用して、挿入のためにバッチをSQLに渡します。

Buld挿入は次のようになります。

INSERT INTO [dbo].[Attributes] ([Name],[PetName]) VALUES ('A', 'A'),('B', 'B'),('C', 'C'),('D', 'D'),('E', 'E'),('F', 'F'),('G', 'G'),('H', 'H'),('I', 'I'),('J', 'J'),('K', 'K'),('L', 'L'),('M', 'M'),('N', 'N'),('O', 'O'),('P', 'P'),('Q', 'Q'),('R', 'R'),('S', 'S')

これだけで、挿入速度が1トン速くなるはずです。

  1. 25以上の論理コアがない限り、前述のように50スレッドを使用しないでください。 SQL挿入時間のほとんどはネットワークで待機し、ハードドライブはCPUではありません。その多くのスレッドをキューに入れることにより、スタックのより遅い部分を待つためにCPU時間のほとんどが予約されます。

私が想像するこれら2つのことだけで、挿入を数分に短縮できます(基本的にこのアプローチを使用して、約90秒で80k +を一度実行しました)。

最後の部分は、各コアが独自のSql接続を取得するようにリファクタリングし、すべてのスレッドを破棄する準備ができるまで開いたままにしておきます。

2
Austin T French

SqlCmd を使用してみてください。

Process.Start() を使用して複数のプロセスを実行し、sqlcmdを使用して並列プロセスでクエリを実行できます。

もちろん、スレッドで行う義務がある場合、この答えはもはや解決策にはなりません。

私はpowershellについてよく知りませんが、SQLはC#で常に仕事をしています。

C#の新しいasync/awaitキーワードにより、話していることを非常に簡単に実行できます。 C#は、マシンに最適なスレッド数のスレッドプールも作成します。

async Task<DataTable> ExecuteQueryAsync(query)
{
    return await Task.Run(() => ExecuteQuerySync(query));
}

async Task ExecuteAllQueriesAsync()
{
    IList<Task<DataTable>> queryTasks = new List<Task<DataTable>>();

    foreach query
    {
         queryTasks.Add(ExecuteQueryAsync(query));
    }

    foreach task in queryTasks
    {
         await task;
    }
}

上記のコードは、すべてのクエリをスレッドプールの作業キューに追加します。その後、完了する前にそれらをすべて待ちます。その結果、SQLの並列処理が最大レベルに達します。

お役に立てれば!

1
c.abate