You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用TarWriter结合SqlClient流生成Tar文件时内存占用过高问题

从SQL Server读取大字段生成Tar文件时内存占用过高的问题

我之前解决了从varbinary(max)列在内存中生成Tar文件的需求,但现在要处理单文件约2GB、总大小约20GB的大型Tar文件,MemoryStream已无法满足——服务器分配不出这么大的内存。

参考相关低内存占用的Tar打包方案后,我修改了实现逻辑:从SQL Server读取varbinary列,生成磁盘临时Tar文件后流式返回给客户端。

当前代码如下:

var tempFileName = Path.GetTempFileName();
await using var tarFileStream = File.Create(tempFileName);

await using (var tarWriter = new TarWriter(tarFileStream, leaveOpen: true))
{
    await using var connection = new SqlConnection("YourConnectionString");
    const string query = @"
SELECT
  FileName,
  FileData
FROM FilesTable;
";
    await using var command = new SqlCommand(query, connection);
    await connection.OpenAsync();
    await using var reader = command.ExecuteReaderAsync(CommandBehavior.SequentialAccess);

    while (await reader.ReadAsync())
    {                            
        var fileName = reader.GetString(0);
        await using var fileDataStream = reader.GetStream(1);

        var entry = new PaxTarEntry(TarEntryType.RegularFile, fileName)
        {
            DataStream = fileDataStream,
        };
        await tarWriter.WriteEntryAsync(entry);
    }
}

tarFileStream.Position = 0;

// Stream tarFileStream to response body..

执行这段代码处理SQL Server(FILESTREAM)中几个1.7GB的文件时,大对象堆(LOH)分配了约8GB内存。通过JetBrains DotMemory检测,内存分配集中在TarWriter底层的MemoryStream中。按照流式处理的预期,不该占用这么多内存,我是不是哪里遗漏了?

对比测试:从磁盘读取无内存问题

当我从磁盘读取同一大文件写入TarWriter时,没有出现内存占用过高的情况:

await using var tempFileStream = File.Create(Path.GetTempFileName());
await using (var tarWriter = new TarWriter(tempFileStream, leaveOpen: true))
{
    await using var file = File.Open(@"C:\Users\xyz\Desktop\BigFile.txt", FileMode.Open);
    await tarWriter.WriteEntryAsync(new PaxTarEntry(TarEntryType.RegularFile, Guid.NewGuid().ToString())
    {
        DataStream = file
    });
}

这说明问题必然出在DataStream来自SqlClient的时候。

DotMemory截图显示:

  • 第一张截图中,MemoryStream实例占用大量内存,关联到TarWriter的内部逻辑
  • 第二张截图指向Microsoft.Data.SqlClient相关的内存分配节点

可复现示例

我做了一个可复现的测试:把836MB的大文本文件插入数据库(不使用FILESTREAM),通过GetStream读取并写入TarWriter:

using System.Data;
using System.Formats.Tar;
using Microsoft.Data.SqlClient;

const string connectionString = "Integrated Security=true;Data Source=localhost;Initial Catalog=MyTestDatabase;" +
                                "User Id=username;Password=xy;Max pool size=200;Min pool size=10;Connection Timeout=30;" +
                                "Encrypt=false";

await using var connection = new SqlConnection(connectionString);
await using var largeFile = File.Open(@"C:\Users\xyz\Desktop\BigFile.txt", FileMode.Open);

await using var createAndInsertCommand = new SqlCommand(
    """
    CREATE TABLE [dbo].[Temp] ([Id] INT NOT NULL, [Data] VARBINARY (MAX) NOT NULL);
    INSERT INTO dbo.Temp VALUES (@Id, @Data);
    """, 
    connection);
createAndInsertCommand.Parameters.Add("@Id", SqlDbType.Int).Value = 1;
createAndInsertCommand.Parameters.Add("@Data", SqlDbType.VarBinary, size: -1).Value = largeFile;
await createAndInsertCommand.Connection.OpenAsync();
await createAndInsertCommand.ExecuteNonQueryAsync();

// Read from database
await using var command = new SqlCommand("SELECT TOP 1 Data FROM dbo.Temp", connection);
await using var reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess);

if (await reader.ReadAsync())
{
    await using var fileData = reader.GetStream(0);
    
    await using var tempFileStream = File.Create(Path.GetTempFileName());
    await using var tarWriter = new TarWriter(tempFileStream, leaveOpen: true, format: TarEntryFormat.Pax);


    tarWriter.WriteEntry(new PaxTarEntry(TarEntryType.RegularFile, Guid.NewGuid().ToString())
    {
        DataStream = fileData
    });
}

对应的DotMemory截图显示:

  • 第一个截图中,MemoryStream占用了近1GB内存
  • 第二个截图中,内存分配追溯到SqlDataReader的流读取逻辑

内容的提问来源于stack exchange,提问作者Filip

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 15:15:07