Powershell StringBuilder 性能差?
Powershell poor StringBuilder performance?
我正在尝试构建查询,以便将大量数据插入 sqlite3 table。我已经尝试了几种方法来执行此操作,包括 PSSQLite ,它应该能够获取 DataTable 并轻松插入它。即使只有 10,000 条记录,运行 也需要将近 40 分钟,我不知道为什么。
我的另一个选择是构建查询并使用其他方法(例如 Invoke-Sqlcmd)执行它。我已经尝试使用 StringBuilder 执行此操作,只是构建字符串需要 2 分钟以上的时间。就像我说的,它只有 10,000 条记录,所以从我准备好的情况来看,它应该只需要 10-15 秒 TOPS。考虑到我至少有几百万条记录要导入,我真的需要它来加快速度。
这是我正在使用的代码。我是不是漏掉了什么?
$sb = [System.Text.StringBuilder]::new()
$sb.AppendLine("BEGIN TRANSACTION")
foreach ($document in $documents) {
$null = $sb.AppendLine("INSERT or IGNORE into documents(DocId,Submid,docno,Tray,Pieceno,CreateDate,Account,AccName,AccAddr1,AccAddr2,AccAddr3,AccAddr4,AccCity,AccState,AccZip,BCDP,BarcodeID,ServTypeID,Mailerid,SerialNo,Sys_Name,Sys_Addr1,Sys_Addr2,Sys_Addr3,Sys_Addr4,Sys_City,Sys_State,Sys_Zip)");
$null = $sb.AppendLine("VALUES('$($document.DocId)','$($document.Submid)','$($document.docno)','$($document.Tray)','$($document.Pieceno)','$($document.CreateDate)','$($document.Account)','$($document.AccName)','$($document.AccAddr1)','$($document.AccAddr2)','$($document.AccAddr3)','$($document.AccAddr4)','$($document.AccCity)','$($document.AccState)','$($document.AccZip)','$($document.BCDP)','$($document.BarcodeID)','$($document.ServTypeID)','$($document.Mailerid)','$($document.SerialNo)','$($document.Sys_Name)','$($document.Sys_Addr1)','$($document.Sys_Addr2)','$($document.Sys_Addr3)','$($document.Sys_Addr4)','$($document.Sys_City)','$($document.Sys_State)','$($document.Sys_Zip)')")
}
$sb.AppendLine("COMMIT")
$query = $sb.ToString();
#Invoke-SqliteQuery $ref_db $query #commenting this out, because I haven't even attempted the insert because StringBuilder is not optimized enough yet.
在此实例中,$documents 是一个通用对象,包含 INSERT 语句中的每个字段。大多数字段都填充了一个字符串,其中一些是空白的。
#EDIT:我运行在 Powershell ISE 中设置了断点,这会导致性能问题吗?
您是否尝试过使用管道和 -Join
运算符的 PowerShell 方式?
$sb = ($Documents | Foreach { "BEGIN TRANSACTION" } {
"INSERT or IGNORE into documents(DocId,Submid,docno,Tray,Pieceno,CreateDate,Account,AccName,AccAddr1,AccAddr2,AccAddr3,AccAddr4,AccCity,AccState,AccZip,BCDP,BarcodeID,ServTypeID,Mailerid,SerialNo,Sys_Name,Sys_Addr1,Sys_Addr2,Sys_Addr3,Sys_Addr4,Sys_City,Sys_State,Sys_Zip)"
"VALUES('$($_.DocId)','$($_.Submid)','$($_.docno)','$($_.Tray)','$($_.Pieceno)','$($_.CreateDate)','$($_.Account)','$($_.AccName)','$($_.AccAddr1)','$($_.AccAddr2)','$($_.AccAddr3)','$($_.AccAddr4)','$($_.AccCity)','$($_.AccState)','$($_.AccZip)','$($_.BCDP)','$($_.BarcodeID)','$($_.ServTypeID)','$($_.Mailerid)','$($_.SerialNo)','$($_.Sys_Name)','$($_.Sys_Addr1)','$($_.Sys_Addr2)','$($_.Sys_Addr3)','$($_.Sys_Addr4)','$($_.Sys_City)','$($_.Sys_State)','$($_.Sys_Zip)')"
} { "COMMIT" }) -Join [Environment]::NewLine
或者(因为 Foreach
语句通常比 Foreach-Object
cmdlet 快一点):
$sb = @(
"BEGIN TRANSACTION"
foreach ($document in $documents) {
"INSERT or IGNORE into documents(DocId,Submid,docno,Tray,Pieceno,CreateDate,Account,AccName,AccAddr1,AccAddr2,AccAddr3,AccAddr4,AccCity,AccState,AccZip,BCDP,BarcodeID,ServTypeID,Mailerid,SerialNo,Sys_Name,Sys_Addr1,Sys_Addr2,Sys_Addr3,Sys_Addr4,Sys_City,Sys_State,Sys_Zip)"
"VALUES('$($_.DocId)','$($_.Submid)','$($_.docno)','$($_.Tray)','$($_.Pieceno)','$($_.CreateDate)','$($_.Account)','$($_.AccName)','$($_.AccAddr1)','$($_.AccAddr2)','$($_.AccAddr3)','$($_.AccAddr4)','$($_.AccCity)','$($_.AccState)','$($_.AccZip)','$($_.BCDP)','$($_.BarcodeID)','$($_.ServTypeID)','$($_.Mailerid)','$($_.SerialNo)','$($_.Sys_Name)','$($_.Sys_Addr1)','$($_.Sys_Addr2)','$($_.Sys_Addr3)','$($_.Sys_Addr4)','$($_.Sys_City)','$($_.Sys_State)','$($_.Sys_Zip)')"
}
"COMMIT"
) -Join [Environment]::NewLine
在进入问题的 StringBuilder
方面之前,让我们先看一下 SQLite:
如果您将 SQL 更改为提交 单个 multi-row INSERT
,我希望您会看到处理时间的差异声明,而不是 10000 个单独的 INSERT
,就像您现在所做的那样 - 换句话说:
$null = $sb.AppendLine("INSERT or IGNORE into documents(DocId,Submid,docno,Tray,Pieceno,CreateDate,Account,AccName,AccAddr1,AccAddr2,AccAddr3,AccAddr4,AccCity,AccState,AccZip,BCDP,BarcodeID,ServTypeID,Mailerid,SerialNo,Sys_Name,Sys_Addr1,Sys_Addr2,Sys_Addr3,Sys_Addr4,Sys_City,Sys_State,Sys_Zip)")
$null = $sb.AppendLine("VALUES")
foreach ($document in $documents) {
# Add separate value tuple for each document, add trailing `,`
$null = $sb.AppendLine("('$($document.DocId)','$($document.Submid)','$($document.docno)','$($document.Tray)','$($document.Pieceno)','$($document.CreateDate)','$($document.Account)','$($document.AccName)','$($document.AccAddr1)','$($document.AccAddr2)','$($document.AccAddr3)','$($document.AccAddr4)','$($document.AccCity)','$($document.AccState)','$($document.AccZip)','$($document.BCDP)','$($document.BarcodeID)','$($document.ServTypeID)','$($document.Mailerid)','$($document.SerialNo)','$($document.Sys_Name)','$($document.Sys_Addr1)','$($document.Sys_Addr2)','$($document.Sys_Addr3)','$($document.Sys_Addr4)','$($document.Sys_City)','$($document.Sys_State)','$($document.Sys_Zip)'),")
}
# trim trailing newline + comma on last insert value before adding COMMIT statement
$query = $sb.ToString().TrimEnd("`r`n,") + "`r`nCOMMIT"
Invoke-SqliteQuery $ref_db $query
通过避免使用可扩展字符串并使用 $sb.AppendFormat()
来进行微小的优化,即:
$sb = [System.Text.StringBuilder]::new()
$sb.AppendLine("BEGIN TRANSACTION")
$null = $sb.AppendFormat('VALUES ({0}, {2}, ...)', $doc.DocId, $doc.SubmId, ...).AppendLine()
...但这可能不是问题。
在 Windows PowerShell 中,字符串操作(无论是通过直接连接还是使用字符串构建)一旦大小超过 very funky performance characteristics .NET Framework 中大型对象堆缓存 (85Kb) 的阈值。
这 似乎不会出现在 .NET Core 中,因此升级到更新版本的 PowerShell(例如 PowerShell 7)可能会一起消除这个问题。
如果您需要针对 Windows PowerShell,您可能只想将 SQL 脚本直接写入磁盘并使用 Invoke-SqliteQuery -InputFile
:
读回
$scriptFile = New-Item import.sql
try{
$fileWriter = $scriptFile.CreateText()
$fileWriter.WriteLine("INSERT or IGNORE into documents(DocId,Submid,docno,Tray,Pieceno,CreateDate,Account,AccName,AccAddr1,AccAddr2,AccAddr3,AccAddr4,AccCity,AccState,AccZip,BCDP,BarcodeID,ServTypeID,Mailerid,SerialNo,Sys_Name,Sys_Addr1,Sys_Addr2,Sys_Addr3,Sys_Addr4,Sys_City,Sys_State,Sys_Zip)")
foreach ($document in $documents) {
$fileWriter.WriteLine("VALUES")
$fileWriter.WriteLine("('$($document.DocId)','$($document.Submid)','$($document.docno)','$($document.Tray)','$($document.Pieceno)','$($document.CreateDate)','$($document.Account)','$($document.AccName)','$($document.AccAddr1)','$($document.AccAddr2)','$($document.AccAddr3)','$($document.AccAddr4)','$($document.AccCity)','$($document.AccState)','$($document.AccZip)','$($document.BCDP)','$($document.BarcodeID)','$($document.ServTypeID)','$($document.Mailerid)','$($document.SerialNo)','$($document.Sys_Name)','$($document.Sys_Addr1)','$($document.Sys_Addr2)','$($document.Sys_Addr3)','$($document.Sys_Addr4)','$($document.Sys_City)','$($document.Sys_State)','$($document.Sys_Zip)')")
$fileWriter.WriteLine("")
}
$fileWriter.WriteLine("COMMIT")
}
finally{
$fileWriter.Close()
}
Invoke-SqliteQuery $ref_db -InputFile import.sql
我正在尝试构建查询,以便将大量数据插入 sqlite3 table。我已经尝试了几种方法来执行此操作,包括 PSSQLite ,它应该能够获取 DataTable 并轻松插入它。即使只有 10,000 条记录,运行 也需要将近 40 分钟,我不知道为什么。
我的另一个选择是构建查询并使用其他方法(例如 Invoke-Sqlcmd)执行它。我已经尝试使用 StringBuilder 执行此操作,只是构建字符串需要 2 分钟以上的时间。就像我说的,它只有 10,000 条记录,所以从我准备好的情况来看,它应该只需要 10-15 秒 TOPS。考虑到我至少有几百万条记录要导入,我真的需要它来加快速度。
这是我正在使用的代码。我是不是漏掉了什么?
$sb = [System.Text.StringBuilder]::new()
$sb.AppendLine("BEGIN TRANSACTION")
foreach ($document in $documents) {
$null = $sb.AppendLine("INSERT or IGNORE into documents(DocId,Submid,docno,Tray,Pieceno,CreateDate,Account,AccName,AccAddr1,AccAddr2,AccAddr3,AccAddr4,AccCity,AccState,AccZip,BCDP,BarcodeID,ServTypeID,Mailerid,SerialNo,Sys_Name,Sys_Addr1,Sys_Addr2,Sys_Addr3,Sys_Addr4,Sys_City,Sys_State,Sys_Zip)");
$null = $sb.AppendLine("VALUES('$($document.DocId)','$($document.Submid)','$($document.docno)','$($document.Tray)','$($document.Pieceno)','$($document.CreateDate)','$($document.Account)','$($document.AccName)','$($document.AccAddr1)','$($document.AccAddr2)','$($document.AccAddr3)','$($document.AccAddr4)','$($document.AccCity)','$($document.AccState)','$($document.AccZip)','$($document.BCDP)','$($document.BarcodeID)','$($document.ServTypeID)','$($document.Mailerid)','$($document.SerialNo)','$($document.Sys_Name)','$($document.Sys_Addr1)','$($document.Sys_Addr2)','$($document.Sys_Addr3)','$($document.Sys_Addr4)','$($document.Sys_City)','$($document.Sys_State)','$($document.Sys_Zip)')")
}
$sb.AppendLine("COMMIT")
$query = $sb.ToString();
#Invoke-SqliteQuery $ref_db $query #commenting this out, because I haven't even attempted the insert because StringBuilder is not optimized enough yet.
在此实例中,$documents 是一个通用对象,包含 INSERT 语句中的每个字段。大多数字段都填充了一个字符串,其中一些是空白的。
#EDIT:我运行在 Powershell ISE 中设置了断点,这会导致性能问题吗?
您是否尝试过使用管道和 -Join
运算符的 PowerShell 方式?
$sb = ($Documents | Foreach { "BEGIN TRANSACTION" } {
"INSERT or IGNORE into documents(DocId,Submid,docno,Tray,Pieceno,CreateDate,Account,AccName,AccAddr1,AccAddr2,AccAddr3,AccAddr4,AccCity,AccState,AccZip,BCDP,BarcodeID,ServTypeID,Mailerid,SerialNo,Sys_Name,Sys_Addr1,Sys_Addr2,Sys_Addr3,Sys_Addr4,Sys_City,Sys_State,Sys_Zip)"
"VALUES('$($_.DocId)','$($_.Submid)','$($_.docno)','$($_.Tray)','$($_.Pieceno)','$($_.CreateDate)','$($_.Account)','$($_.AccName)','$($_.AccAddr1)','$($_.AccAddr2)','$($_.AccAddr3)','$($_.AccAddr4)','$($_.AccCity)','$($_.AccState)','$($_.AccZip)','$($_.BCDP)','$($_.BarcodeID)','$($_.ServTypeID)','$($_.Mailerid)','$($_.SerialNo)','$($_.Sys_Name)','$($_.Sys_Addr1)','$($_.Sys_Addr2)','$($_.Sys_Addr3)','$($_.Sys_Addr4)','$($_.Sys_City)','$($_.Sys_State)','$($_.Sys_Zip)')"
} { "COMMIT" }) -Join [Environment]::NewLine
或者(因为 Foreach
语句通常比 Foreach-Object
cmdlet 快一点):
$sb = @(
"BEGIN TRANSACTION"
foreach ($document in $documents) {
"INSERT or IGNORE into documents(DocId,Submid,docno,Tray,Pieceno,CreateDate,Account,AccName,AccAddr1,AccAddr2,AccAddr3,AccAddr4,AccCity,AccState,AccZip,BCDP,BarcodeID,ServTypeID,Mailerid,SerialNo,Sys_Name,Sys_Addr1,Sys_Addr2,Sys_Addr3,Sys_Addr4,Sys_City,Sys_State,Sys_Zip)"
"VALUES('$($_.DocId)','$($_.Submid)','$($_.docno)','$($_.Tray)','$($_.Pieceno)','$($_.CreateDate)','$($_.Account)','$($_.AccName)','$($_.AccAddr1)','$($_.AccAddr2)','$($_.AccAddr3)','$($_.AccAddr4)','$($_.AccCity)','$($_.AccState)','$($_.AccZip)','$($_.BCDP)','$($_.BarcodeID)','$($_.ServTypeID)','$($_.Mailerid)','$($_.SerialNo)','$($_.Sys_Name)','$($_.Sys_Addr1)','$($_.Sys_Addr2)','$($_.Sys_Addr3)','$($_.Sys_Addr4)','$($_.Sys_City)','$($_.Sys_State)','$($_.Sys_Zip)')"
}
"COMMIT"
) -Join [Environment]::NewLine
在进入问题的 StringBuilder
方面之前,让我们先看一下 SQLite:
如果您将 SQL 更改为提交 单个 multi-row INSERT
,我希望您会看到处理时间的差异声明,而不是 10000 个单独的 INSERT
,就像您现在所做的那样 - 换句话说:
$null = $sb.AppendLine("INSERT or IGNORE into documents(DocId,Submid,docno,Tray,Pieceno,CreateDate,Account,AccName,AccAddr1,AccAddr2,AccAddr3,AccAddr4,AccCity,AccState,AccZip,BCDP,BarcodeID,ServTypeID,Mailerid,SerialNo,Sys_Name,Sys_Addr1,Sys_Addr2,Sys_Addr3,Sys_Addr4,Sys_City,Sys_State,Sys_Zip)")
$null = $sb.AppendLine("VALUES")
foreach ($document in $documents) {
# Add separate value tuple for each document, add trailing `,`
$null = $sb.AppendLine("('$($document.DocId)','$($document.Submid)','$($document.docno)','$($document.Tray)','$($document.Pieceno)','$($document.CreateDate)','$($document.Account)','$($document.AccName)','$($document.AccAddr1)','$($document.AccAddr2)','$($document.AccAddr3)','$($document.AccAddr4)','$($document.AccCity)','$($document.AccState)','$($document.AccZip)','$($document.BCDP)','$($document.BarcodeID)','$($document.ServTypeID)','$($document.Mailerid)','$($document.SerialNo)','$($document.Sys_Name)','$($document.Sys_Addr1)','$($document.Sys_Addr2)','$($document.Sys_Addr3)','$($document.Sys_Addr4)','$($document.Sys_City)','$($document.Sys_State)','$($document.Sys_Zip)'),")
}
# trim trailing newline + comma on last insert value before adding COMMIT statement
$query = $sb.ToString().TrimEnd("`r`n,") + "`r`nCOMMIT"
Invoke-SqliteQuery $ref_db $query
通过避免使用可扩展字符串并使用 $sb.AppendFormat()
来进行微小的优化,即:
$sb = [System.Text.StringBuilder]::new()
$sb.AppendLine("BEGIN TRANSACTION")
$null = $sb.AppendFormat('VALUES ({0}, {2}, ...)', $doc.DocId, $doc.SubmId, ...).AppendLine()
...但这可能不是问题。
在 Windows PowerShell 中,字符串操作(无论是通过直接连接还是使用字符串构建)一旦大小超过 very funky performance characteristics .NET Framework 中大型对象堆缓存 (85Kb) 的阈值。
这 似乎不会出现在 .NET Core 中,因此升级到更新版本的 PowerShell(例如 PowerShell 7)可能会一起消除这个问题。
如果您需要针对 Windows PowerShell,您可能只想将 SQL 脚本直接写入磁盘并使用 Invoke-SqliteQuery -InputFile
:
$scriptFile = New-Item import.sql
try{
$fileWriter = $scriptFile.CreateText()
$fileWriter.WriteLine("INSERT or IGNORE into documents(DocId,Submid,docno,Tray,Pieceno,CreateDate,Account,AccName,AccAddr1,AccAddr2,AccAddr3,AccAddr4,AccCity,AccState,AccZip,BCDP,BarcodeID,ServTypeID,Mailerid,SerialNo,Sys_Name,Sys_Addr1,Sys_Addr2,Sys_Addr3,Sys_Addr4,Sys_City,Sys_State,Sys_Zip)")
foreach ($document in $documents) {
$fileWriter.WriteLine("VALUES")
$fileWriter.WriteLine("('$($document.DocId)','$($document.Submid)','$($document.docno)','$($document.Tray)','$($document.Pieceno)','$($document.CreateDate)','$($document.Account)','$($document.AccName)','$($document.AccAddr1)','$($document.AccAddr2)','$($document.AccAddr3)','$($document.AccAddr4)','$($document.AccCity)','$($document.AccState)','$($document.AccZip)','$($document.BCDP)','$($document.BarcodeID)','$($document.ServTypeID)','$($document.Mailerid)','$($document.SerialNo)','$($document.Sys_Name)','$($document.Sys_Addr1)','$($document.Sys_Addr2)','$($document.Sys_Addr3)','$($document.Sys_Addr4)','$($document.Sys_City)','$($document.Sys_State)','$($document.Sys_Zip)')")
$fileWriter.WriteLine("")
}
$fileWriter.WriteLine("COMMIT")
}
finally{
$fileWriter.Close()
}
Invoke-SqliteQuery $ref_db -InputFile import.sql