在 PowerShell 中将大型 blob 从 SQL Server 提取到文件需要很长时间

Posted

技术标签:

【中文标题】在 PowerShell 中将大型 blob 从 SQL Server 提取到文件需要很长时间【英文标题】:Extracting a large blob from SQL Server to a file takes a very long time in PowerShell 【发布时间】:2021-12-01 16:32:49 【问题描述】:

我一直在研究一种将 blob 列自动提取到文件的方法,blog 详细介绍了一些方法。

通过 BCP,可以非常快速地从我的数据库中提取较大的文件。我能够在 20 秒内提取 2 GB 的文件。这是我使用的示例命令行,基于博客中的示例:

BCP "SELECT PictureData FROM BLOB_Test.dbo.PicturesTest " QUERYOUT C:\BLOBTest\BlobOut\WC.jpg -T -f "C:\BLOBTest\FormatFile\BLOB.fmt" -S <ServerName>\<InstanceName>

顺便说一句,我必须学习如何应用格式文件来防止将前缀字符串插入文件中。此格式文件必须采用 BCP 的旧格式,因为格式文件的较新 XML 版本具有“PREFIX_LENGTH”的架构条目,可防止 0 值。

我宁愿使用 PowerShell 来提取 blob,但以下基于 TechNet article 的代码需要大约两天的时间来处理,而不是 BCP 的 20 秒来处理相同的 2 gig blob。

## https://social.technet.microsoft.com/wiki/contents/articles/890.export-sql-server-blob-data-with-powershell.aspx
## Export of "larger" SQL Server blob to file with GetBytes-Stream

# Configuration data
   $Server     = ".\<Instance>";         # SQL Server Instance
   $Database   = "Blob_Test";            # Name of database
   $Dest       = "C:\BLOBTest\BLOBOut\"; # Path to export to
   $bufferSize = 8192;                   # Stream buffer size in bytes

# Select-Statement for name & blob with filter
   $Sql = "Select 
              [PictureName],
              [PictureData]
           From 
              dbo.PicturesTest";

# Open ADO.NET Connection
   $con = New-Object Data.SqlClient.SqlConnection;
   $con.ConnectionString = "Data Source=$Server;" +
                           "Integrated Security=True;" +
                           "Initial Catalog=$Database";
   $con.Open();

# New Command and Reader
   $cmd = New-Object Data.SqlClient.SqlCommand $Sql, $con;
   $rd  = $cmd.ExecuteReader();

# Create a byte array for the stream
   $out = [array]::CreateInstance('Byte', $bufferSize)

# Loop through records
   While ($rd.Read()) 
      Write-Output ("Exporting: 0" -f $rd.GetString(0));
      
      # New BinaryWriter
         $fs = New-Object System.IO.FileStream ($Dest + $rd.GetString(0)), Create, Write;
         $bw = New-Object System.IO.BinaryWriter $fs;
      
         $start = 0;

      # Read first byte stream
         $received = $rd.GetBytes(1, $start, $out, 0, $bufferSize - 1);
      
      While ($received -gt 0) 
         $bw.Write($out, 0, $received);
         $bw.Flush();
         $start += $received;
         
         # Read next byte stream
            $received = $rd.GetBytes(1, $start, $out, 0, $bufferSize - 1);
      
      
      $bw.Close();
      $fs.Close();
   

# Closing & disposing all objects
   $fs.Dispose();
   $rd.Close();
   $cmd.Dispose();
   $con.Close();

Write-Output ("Finished");

它最终会完成,但我不知道为什么脚本需要这么长时间才能完成。

有人知道为什么 PowerShell 脚本会被绝育吗?

【问题讨论】:

您根本不需要 BinaryWriter。该类旨在以 .NET 特定格式编写原始类型。您可以使用DbDataReader.GetStream 获取二进制数据的流,并使用Stream.CopyTo 从该流复制到文件流,只需两行,例如:$dbFs=$rd.GetStream(1); $dbFs.CopyTo($fs); 即使您想使用字节,也可以使用Stream.Write 将字节数组写入流。你不需要 BinaryWriter 旁注应该在try/finally中处理,这样即使有异常它也会一直执行 【参考方案1】:

您根本不需要 BinaryWriter。该类旨在以特定于 .NET 的格式编写原始类型,如整数、双精度数、字符串等。很少使用。

如果您想将字节写入文件,您只需使用Stream.Write:

$fs.Write $received

可以消除几乎所有代码的更好主意是使用DbDataReader.GetStream 而不是GetBytes 将BLOB 作为流读取。之后您可以使用Stream.CopyTo 将流的内容写入另一个流:

$dbFs=$rd.GetStream(1); 
$dbFs.CopyTo($fs);

【讨论】:

谢谢!这将节省我几个月的时间。它真的很快。我很惊讶没有其他突出的例子使用流技术。只是好奇,有什么想法为什么二进制编写器如此缓慢?【参考方案2】:

这个 2010 年的脚本仍然有效并且运行速度非常快。

$server = ".";
$database = "YourDatab";
$query = "SELECT FileContent,FileName FROM dbo.FileUploads";
$dirPath = "C:\Data\"
 
$connection=new-object System.Data.SqlClient.SQLConnection
$connection.ConnectionString="Server=0;Database=1;Integrated Security=True" -f $server,$database
$command=new-object system.Data.SqlClient.SqlCommand($query,$connection)
$command.CommandTimeout=120
$connection.Open()
$reader = $command.ExecuteReader()
while ($reader.Read())

    $sqlBytes = $reader.GetSqlBytes(0)
    $filepath = "$dirPath0" -f $reader.GetValue(1)
    $buffer = new-object byte[] -ArgumentList $reader.GetBytes(0,0,$null,0,$sqlBytes.Length)
    $reader.GetBytes(0,0,$buffer,0,$buffer.Length)
    $fs = new-object System.IO.FileStream($filePath,[System.IO.FileMode]'Create',[System.IO.FileAccess]'Write')
    $fs.Write($buffer, 0, $buffer.Length)
    $fs.Close()

$reader.Close()
$connection.Close()

来源:https://www.sqlservercentral.com/blogs/t-sql-tuesday-006-blobs-filestream-and-powershell

【讨论】:

以上是关于在 PowerShell 中将大型 blob 从 SQL Server 提取到文件需要很长时间的主要内容,如果未能解决你的问题,请参考以下文章

Powershell脚本从blob容器的子文件夹中删除文件

用于从存储帐户 V2 检索存储帐户资源 ID、blob 资源 ID 的 Powershell 命令

使用 powershell 在 Azure 中不同订阅的存储容器之间复制 blob

如何在颤动中将 Blob 转换为图像

是否可以使用 powershell 在存储帐户中创建 blob?

在android中将图像作为BLOB上传到SQL