【问题标题】:SQL Server Filetables/FileStream: Insert more than 2GB via Transact SQL?SQL Server Filetables/FileStream:通过 Transact SQL 插入超过 2GB?
【发布时间】:2019-09-04 06:19:05
【问题描述】:

我想使用 T-SQL 将文件复制到 SQL Server 文件表。我不能使用 Win32 API(或SqlFileStream 类),因为服务器不公开 SMB。以下代码非常适用于较小的文件(最大 1GB):

using (SqlConnection conn = new SqlConnection(connStr))
using (SqlCommand cmd = conn.CreateCommand())
using (Stream file = File.OpenRead(@"c:\Path\To\Large\file.bin"))
{
    conn.Open();
    cmd.CommandTimeout = 3600;
    cmd.CommandText = "UPDATE file_table SET file_stream = @stream WHERE path_locator = GetPathLocator(@path)";
    cmd.Parameters.AddWithValue("@path", @"\\HOST\INSTANCE\DB\file_table\largetest.bin");
    cmd.Parameters.Add("@stream", System.Data.SqlDbType.Binary, -1).Value = file;
    cmd.ExecuteNonQuery();
}

性能不错(大约 100MB/s),并且传递流也很有效,并且很容易占用客户端的内存。但是,我看到在此查询期间服务器的内存使用量激增 - 显然,SQL Server 将整个流复制到内存中,然后再将其写入磁盘。我还看到 tempdb 的磁盘 IO。查询有时会成功,但对于同一文件,有时会失败。文件大小在 1.5GB 左右开始出现错误我从来没有成功处理过 4GB 的文件。如果查询失败,我会收到以下异常: System.Data.SqlClient.SqlException: 'Internal error: An expression services limit has been reached. Please look for potentially complex expressions in your query, and try to simplify them.'

有没有更好的方法将大文件放入文件表中?

【问题讨论】:

  • 您是否正在将 2GB 的 blob 加载到数据库中?如果是,为什么?
  • @gvee SQL Filetables 实际上是用来存储大文件的。它实际上并不将它们存储在数据库中,而是将它们存储在文件系统中并为其提供 T-SQL 接口。原因是我的应用程序需要一个位置来存储所有客户端的文件以及一个数据库,而我只想为两者公开一个服务器接口。

标签: c# sql-server filestream sqlclient filetable


【解决方案1】:

我建议在此过程中使用BCP。然后调用存储过程从临时表中更新。

cmd.CommandText= "exec xp_cmdShell 'bcp.exe'"+Database + ".." + TableName + " in "  + 
                 c:\Path\To\Large\file.bin + " -c -q -U " + UserId + " -P " + Password +  "-t  ";

【讨论】:

    【解决方案2】:

    我最终为此使用了SqlBulkCopy。我没有可复制和可粘贴的工作示例,因此我将描述到达那里的步骤。

    这个想法是 stolen from this answer 并适用于 FileTables。由于我们正在处理大文件,因此流式传输数据是一个非常好的主意。 FileTables 在插入新数据时至少需要两列,它们是namefile_stream。但是,这会将新文件放在 FileTable 的根目录中。因此,我们还需要指定path_locator 字段,其类型为HierarchyId。因此,最小的工作 DbDataReader 应该有三列:nameString 类型)、file_pathSqlHierarchyId 类型)和file_streamStream 类型)。您显然拥有name,链接的答案显示了如何获取file_stream 的内容,留下path_locator

    缺少的链接是在代码中生成一个 FileTable HierarchyId(有示例如何在 SQL 中创建它们,但这会很麻烦):

    public string GetNewHierarchyId()
    {
        var g = Guid.NewGuid().ToByteArray();
        var used = 0;
        var strings = new List<string>();
        foreach (var i in new[] { 6, 6, 4 })
        {
            byte[] buf = new byte[8];
            Array.Copy(g, used, buf, (BitConverter.IsLittleEndian ? 0 : 2), i);
            used += i;
            strings.Add(BitConverter.ToUInt64(buf, 0).ToString());
        }
        return string.Join(".", strings) + "/";
    }
    

    要获取新的path_locator,请在新文件的父目录前面加上path_locator,您可以通过SELECT GetPathLocator(@parentDir) FROM filetable 获取它

    接下来,您需要 Microsoft.SqlServer.Types Nuget 包,因为 SqlBulkCopy 需要传递类型为 SqlHierarchyId 的参数,然后您可以像这样创建它:Microsoft.SqlServer.Types.SqlHierarchyId.Parse(parentPathLocator &amp; GetNewHierarchyId())

    我必须修改 app.config 中的 bindingRedirects 以使 Microsoft.Sql.Types 包正常工作,方法是添加:

    <runtime>
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
          <dependentAssembly>
            <assemblyIdentity name="Microsoft.SqlServer.Types" publicKeyToken="89845dcd8080cc91" culture="neutral" />
            <!-- <bindingRedirect oldVersion="0.0.0.0-11.0.0.0" newVersion="11.0.0.0" /> -->
            <bindingRedirect oldVersion="0.0.0.0-14.0.0.0" newVersion="14.0.0.0" />
          </dependentAssembly>
        </assemblyBinding>
      </runtime>
    

    如果您想指定文件的属性,当然可以在SqlBulkCopy 中添加额外的列。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-23
      • 2011-05-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多