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

使用ADO.NET SqlDataReader读取18万条记录时遇内存不足异常求解决

解决SqlDataReader读取大量数据内存溢出的可行方案

1. 延迟加载返回流式数据(最推荐)

不要一次性把所有对象存入List,改用IEnumerable<MyObject>配合yield return延迟生成对象。调用方遍历的时候才会逐个创建并处理对象,内存中始终只保留当前处理的实例,不会一次性占用大量内存。

代码示例:

public IEnumerable<MyObject> GetMyObjects(SqlCommand cmd)
{
    using var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess); // 搭配SequentialAccess进一步优化内存
    while(reader.Read())
    {
        var obj = new MyObject();
        obj.Property1 = Convert.ToInt32(reader["Column1"]);
        obj.Property2 = Convert.ToString(reader["Column2"]);
        // ... 其他176个属性赋值
        obj.Property178 = Convert.ToString(reader["Column178"]);

        yield return obj;
    }
}

调用方处理时直接遍历即可:

foreach(var item in GetMyObjects(cmd))
{
    // 逐个处理item,处理完成后会被GC自动回收
}

2. 数据库分页分批读取

把18万条数据拆分成多批次查询,比如每次读取1000条,处理完当前批次再加载下一批,避免一次性把所有数据加载到内存。

分页SQL示例(SQL Server):

SELECT * FROM (
    SELECT *, ROW_NUMBER() OVER(ORDER BY Column1) AS RowNum
    FROM YourTargetTable
) AS T
WHERE T.RowNum BETWEEN @StartRow AND @EndRow

C#代码示例:

int batchSize = 1000;
// 先获取总记录数,或者循环直到reader读取不到数据为止
int totalCount = GetTotalRecordCount(cmd); 

for(int i = 0; i < totalCount; i += batchSize)
{
    cmd.CommandText = "上述分页SQL语句";
    cmd.Parameters.Clear();
    cmd.Parameters.AddWithValue("@StartRow", i + 1);
    cmd.Parameters.AddWithValue("@EndRow", Math.Min(i + batchSize, totalCount));

    using var reader = cmd.ExecuteReader();
    while(reader.Read())
    {
        var obj = new MyObject();
        // 属性赋值逻辑
        ProcessSingleObject(obj); // 直接处理当前对象,无需存入大集合
    }
}

如果调用方必须拿到完整集合,这种方式不适用;但如果可以分批处理业务逻辑,这是控制内存的有效手段。

3. 优化MyObject的内存占用

3.1 将MyObject改为值类型(struct)

struct是值类型,复用实例时每次赋值都是拷贝操作,不会出现引用类型“所有记录值相同”的问题,同时值类型的内存开销比引用类型更小(无需对象头、GC额外开销)。

修改MyObject定义:

public struct MyObject
{
    public int Property1 { get; set; }
    public string Property2 { get; set; }
    // ... 其他176个属性定义
}

复用实例代码:

List<MyObject> collection = new List<MyObject>();
MyObject obj = new MyObject();
using var reader = cmd.ExecuteReader();

while(reader.Read())
{
    obj.Property1 = Convert.ToInt32(reader["Column1"]);
    obj.Property2 = Convert.ToString(reader["Column2"]);
    // ... 其他属性赋值
    collection.Add(obj); // 值类型会自动拷贝,集合中是独立的实例
}

注意:如果MyObject包含大量字符串等引用类型,优化效果会打折扣,但仍优于类实例。

3.2 缓存重复字符串

如果部分字符串属性存在大量重复值(如状态、类别字段),用字符串池缓存重复实例,减少内存占用:

private static readonly Dictionary<string, string> _stringCache = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

// 赋值时替换为缓存逻辑
obj.Property2 = GetCachedString(Convert.ToString(reader["Column2"]));

private string GetCachedString(string value)
{
    if(string.IsNullOrEmpty(value)) return value;
    if(_stringCache.TryGetValue(value, out var cachedValue))
    {
        return cachedValue;
    }
    _stringCache[value] = value;
    return value;
}

相同的字符串只会在内存中存储一份,能大幅降低重复字符串的内存开销。

4. 启用SqlDataReader的SequentialAccess模式

默认情况下SqlDataReader会把整行数据加载到内存,使用CommandBehavior.SequentialAccess可以让它按需读取列数据,减少内存占用,尤其适合列数较多的场景:

using var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess);

注意:使用该模式时必须按数据库列的顺序读取,不能跳读(比如先读Column178再读Column1),否则会触发错误。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 20:52:05