问题::我有大量的SQL查询(大约10k-20k),我想在50个(或更多)线程中异步运行它们。
我为此工作编写了一个powershell脚本,但是它很慢(执行所有操作大约需要20个小时)。 所需结果最多为3-4小时
问题:如何优化此Powershell脚本?我应该重新考虑并使用python
或c#
之类的其他技术吗?
我认为这是powershell的问题,因为当我使用whoisactive
检查时,查询正在快速执行。创建,退出和卸载作业需要花费大量时间,因为为每个线程创建了单独的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 *
}
更新:
资源:数据库服务器位于具有以下位置的远程计算机上:
我们要使用最大的cpu功率。
框架限制:唯一的限制是 不是 使用SSMS 来执行查询。请求应该来自外部资源,例如:Powershell,C#,Python等。
答案 0 :(得分:4)
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 :(得分:2)
可悲的是,我现在没有时间充分回答这个问题,但这应该有所帮助:
首先,您几乎不会保证要使用整个CPU插入那么多记录。但是!
自从出现以来,您正在使用SQL字符串命令:
像这样的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')
仅此一项就可以使您的刀片加速一吨!
仅凭这两件事,我就可以将您的插入内容缩短到大约几分钟(我使用这种方法在大约90秒内完成了80k次以上操作)。
最后一部分可能是重构的,以便每个内核都有自己的Sql连接,然后将其保持打开状态,直到准备好处置所有线程为止。
答案 2 :(得分:1)
我对powershell不太了解,但是我确实在工作中始终使用C#执行SQL。
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将达到最大并行度。
希望这会有所帮助!
答案 3 :(得分:0)
答案 4 :(得分:0)
您需要重新组织脚本,以使数据库连接在每个工作线程中保持打开状态,并将其用于该线程执行的所有查询。现在,您正在为每个查询打开一个新的数据库连接,这将增加大量的开销。消除开销可以使事情达到或超过目标速度。
答案 5 :(得分:0)
因此,首先研究您的数据集,然后执行上面的两项,以便您可以轻松识别所有并行且有效运行的查询。
希望这会给您一些想法。更好的是,您可以为此使用任何python脚本,以便您可以轻松触发多个进程并监视其活动。