优化PowerShell脚本以进行并行处理

时间:2017-12-21 20:16:44

标签: powershell

我在Windows 2012 R2上有一个PowerShell脚本,用于将数据库中的数据导出到CSV文件中。我在那里检查以避免双引号和文本限定必要的字段。我希望提高脚本的性能,因为它运行速度比我想要的慢一点(导出20GB / 2000万行),它只占用了大约10%的CPU。有没有人有任何改进建议?

$ConnectionString = "Data Source=server1; Database=Development; Trusted_Connection=True;";
$streamWriter = New-Object System.IO.StreamWriter ".\output.csv"
$sqlConn = New-Object System.Data.SqlClient.SqlConnection $ConnectionString
$sqlCmd = New-Object System.Data.SqlClient.SqlCommand
$sqlCmd.Connection = $sqlConn
$sqlCmd.CommandText = "SELECT * FROM Development.dbo.All_Opportunities WITH(NOLOCK)"
$sqlConn.Open();
$reader = $sqlCmd.ExecuteReader();

# Initialze the array the hold the values
$array = @()
for ( $i = 0 ; $i -lt $reader.FieldCount; $i++ ) 
    { $array += @($i) }

# Write Header
$streamWriter.Write($reader.GetName(0))
for ( $i = 1; $i -lt $reader.FieldCount; $i ++) 
{ $streamWriter.Write($("," + $reader.GetName($i))) }

$streamWriter.WriteLine("") # Close the header line

while ($reader.Read())
{
    # get the values;
    $fieldCount = $reader.GetValues($array);

    # add quotes if the values have a comma or double quote
    for ($i = 0; $i -lt $array.Length; $i++)
    {
    if ($array[$i] -match "`"|,")
        {
            $array[$i] = '"' + $array[$i].Replace("`"", "`"`"").ToString() + '"';
        }
    }

    $newRow = [string]::Join(",", $array);

    $streamWriter.WriteLine($newRow)
}
$reader.Close();
$sqlConn.Close();
$streamWriter.Close();

2 个答案:

答案 0 :(得分:0)

您可以将其拆分为作业并在后台启动它们 试试:

https://docs.microsoft.com/en-us/powershell/module/Microsoft.PowerShell.Core/Start-Job?view=powershell-5.1

希望有所帮助

答案 1 :(得分:0)

所以,我在一年前遇到了类似的问题,尽管表格略小(约1 GB)。最初我刚刚使用过:

Import-Module -Name SqlServer -Cmdlet Read-SqlTableData;
Read-SqlTableData -ServerInstance $SqlServer -DatabaseName $Database -SchemaName $Schema -TableName $Table | 
    Export-Csv -Path $OutputFilePath -NoTypeInformation

虽然有效但是它使用了 ton 的内存(16 GB中的5 GB以上)并且运行了大约7到9分钟。所有这些测试都是在笔记本电脑中使用旋转金属盘,所以请记住以下内容。

我想知道我是否可以让它更快。我最初这样编写它,花了大约一半的时间,大约100 MB的RAM:

$SqlServer = '...';
$SqlDatabase = '...';
$OutputFilePath = '...';
$SqlQuery = '...';

$SqlConnectionString = 'Data Source={0};Initial Catalog={1};Integrated Security=SSPI' -f $SqlServer, $SqlDatabase;

$Utf8NoBOM = New-Object -TypeName System.Text.UTF8Encoding -ArgumentList $false;
$StreamWriter = New-Object -TypeName System.IO.StreamWriter -ArgumentList $OutputFilePath, $Utf8NoBOM;

$CsvDelimiter = '"';
$CsvDelimiterEscape = '""';
$CsvSeparator = ',';

$SQLConnection = New-Object -TypeName System.Data.SqlClient.SqlConnection -ArgumentList $SqlConnectionString;
$SqlCommand = $SQLConnection.CreateCommand();
$SqlCommand.CommandText = $SqlQuery;

$SQLConnection.Open();
$SqlDataReader = $SqlCommand.ExecuteReader();

for ($Field = 0; $Field -lt $SqlDataReader.FieldCount; $Field++) {
    if ($Field -gt 0) { $StreamWriter.Write($CsvSeparator); }
    $StreamWriter.Write($CsvDelimiter);
    $StreamWriter.Write($SqlDataReader.GetName($Field).Replace($CsvDelimiter, $CsvDelimiterEscape));
    $StreamWriter.Write($CsvDelimiter);
}
$StreamWriter.WriteLine();

while ($SqlDataReader.Read()) {
    for ($Field = 0; $Field -lt $SqlDataReader.FieldCount; $Field++) {
        if ($Field -gt 0) { $StreamWriter.Write($CsvSeparator); }
        $StreamWriter.Write($CsvDelimiter);
        $StreamWriter.Write($SqlDataReader.GetValue($Field).ToString().Replace($CsvDelimiter, $CsvDelimiterEscape));
        $StreamWriter.Write($CsvDelimiter);
    }
    $StreamWriter.WriteLine();
}

$SqlDataReader.Close();
$SqlDataReader.Dispose();

$SQLConnection.Close();
$SQLConnection.Dispose();

$StreamWriter.Close();
$StreamWriter.Dispose();

正如您所看到的,它与您的模式基本相同。

我想知道我是否可以进一步提高它,所以我尝试添加一个StringBuilder,因为我已经成功地与其他项目一起做了。我仍然有代码,但我发现它没有更快的工作,并占用了大约200 MB的RAM:

$SqlServer = '...'
$SqlDatabase = '...'
$OutputFilePath = '...'
$SqlQuery = '...';

$SqlConnectionString = 'Data Source={0};Initial Catalog={1};Integrated Security=SSPI' -f $SqlServer, $SqlDatabase;

$StringBuilderBufferSize = 50MB;
$StringBuilder = New-Object -TypeName System.Text.StringBuilder -ArgumentList ($StringBuilderBufferSize + 1MB);

$Utf8NoBOM = New-Object -TypeName System.Text.UTF8Encoding -ArgumentList $false;
$StreamWriter = New-Object -TypeName System.IO.StreamWriter -ArgumentList $OutputFilePath, $Utf8NoBOM;

$CsvDelimiter = '"';
$CsvDelimiterEscape = '""';
$CsvSeparator = ',';

$SQLConnection = New-Object -TypeName System.Data.SqlClient.SqlConnection -ArgumentList $SqlConnectionString;
$SqlCommand = $SQLConnection.CreateCommand();
$SqlCommand.CommandText = $SqlQuery;

$SQLConnection.Open();
$SqlDataReader = $SqlCommand.ExecuteReader();

for ($Field = 0; $Field -lt $SqlDataReader.FieldCount; $Field++) {
    if ($Field -gt 0) { [void]$StringBuilder.Append($CsvSeparator); }
    [void]$StringBuilder.Append($CsvDelimiter);
    [void]$StringBuilder.Append($SqlDataReader.GetName($Field).Replace($CsvDelimiter, $CsvDelimiterEscape));
    [void]$StringBuilder.Append($CsvDelimiter);
}
[void]$StringBuilder.AppendLine();

while ($SqlDataReader.Read()) {
    for ($Field = 0; $Field -lt $SqlDataReader.FieldCount; $Field++) {
        if ($Field -gt 0) { [void]$StringBuilder.Append($CsvSeparator); }
        [void]$StringBuilder.Append($CsvDelimiter);
        [void]$StringBuilder.Append($SqlDataReader.GetValue($Field).ToString().Replace($CsvDelimiter, $CsvDelimiterEscape));
        [void]$StringBuilder.Append($CsvDelimiter);
    }
    [void]$StringBuilder.AppendLine();

    if ($StringBuilder.Length -ge $StringBuilderBufferSize) {
        $StreamWriter.Write($StringBuilder.ToString());
        [void]$StringBuilder.Clear();
    }
}

$SqlDataReader.Close();
$SqlDataReader.Dispose();

$SQLConnection.Close();
$SQLConnection.Dispose();

$StreamWriter.Write($StringBuilder.ToString());
$StreamWriter.Close();
$StreamWriter.Dispose();

无论我尝试了什么,我似乎无法在约4:30的时间内获得大约1 GB的数据。

我从未考虑并行性,因为您必须将查询分成4个相等的部分,以便您可以确定您获得完整的数据集,或者使用其他方式执行一些非常困难的流程管理Runspace池。即使这样,您也必须写入四个不同的文件,并最终将这些文件组合在一起。也许它会成功,但在那时我不再是一个有趣的问题。

最后,我刚刚使用“导入导出向导”创建了一个包,将其另存为包,然后使用DTExec.exe运行它。对于1 GB的数据,大约需要45-60秒左右。唯一的缺点是你需要在构建包时指定表,它不会动态确定列,并且输出文件为UTF8是一种不合理的痛苦。

我确实发现bcp.exesqlcmd.exe更快。 BCP非常快,耗时20-30秒。但是,输出格式非常有限,特别是BCP难以使用。