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

Execute Reader触发OutOfMemoryException,请求核实问题勿误判重复

解决大量数据读取时的OutOfMemoryException问题

兄弟,你这问题我太熟了——之前的优化只是把内存溢出的阈值从70万推到了80万,但根本问题没解决:你是在一次性把几十万条记录全部实例化成内存对象,内存再大也扛不住数据量持续增长啊!下面给你几个能真正根治的方案:

1. 分批读取数据(最直接的根治手段)

别想着一次把所有数据拉出来,改成分批从数据库取。给你的存储过程加个分页参数,每次只拉固定数量的记录,处理完一批再取下一批,内存压力直接降下来。

举个存储过程的例子:

CREATE PROCEDURE GetLargeDataBatch
    @PageSize INT,
    @PageNumber INT
AS
BEGIN
    -- 必须加排序,保证分页的一致性,不然不同批次可能重复或漏数据
    SELECT Column1, Column2, Column3 -- 只取需要的字段,别用SELECT *
    FROM YourLargeTable
    ORDER BY YourPrimaryKeyColumn
    OFFSET (@PageNumber - 1) * @PageSize ROWS
    FETCH NEXT @PageSize ROWS ONLY;
END

然后代码里循环调用,边读边处理:

int batchSize = 10000; // 每次取1万条,根据你的内存情况调整,比如8G内存可以调到2万
int currentPage = 1;
bool hasMoreData = true;

while (hasMoreData)
{
    using (var cmd = new SqlCommand("GetLargeDataBatch", yourDbConnection))
    {
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.AddWithValue("@PageSize", batchSize);
        cmd.Parameters.AddWithValue("@PageNumber", currentPage);
        
        using (var reader = cmd.ExecuteReader())
        {
            hasMoreData = reader.HasRows;
            if (!hasMoreData) break;
            
            while (reader.Read())
            {
                // 这里直接处理单条记录,比如写入CSV、导入ES,别存到List里!
                ProcessSingleRecord(reader);
            }
        }
    }
    currentPage++;
    // 处理完一批可以手动触发GC,帮着释放内存(可选,视情况用)
    GC.Collect(2, GCCollectionMode.Forced);
}

2. 开启SqlDataReader的流式访问模式

默认情况下,SqlDataReader会把查询结果缓存到内存里,你可以开启流式模式,让它边从数据库读边处理,不缓存全部数据,内存占用会极低。

开启方式很简单,给ExecuteReader加个CommandBehavior.SequentialAccess参数就行,但要注意:读取字段必须按顺序来,不能跳着读,大字段(比如TEXT、VARBINARY)还要分块读取:

using (var cmd = new SqlCommand("YourOriginalSP", yourDbConnection))
{
    cmd.CommandType = CommandType.StoredProcedure;
    // 开启流式访问,禁用内存缓存
    using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
    {
        while (reader.Read())
        {
            // 按SELECT的字段顺序读取,不能先读第3个再读第1个
            int id = reader.GetInt32(0);
            string name = reader.GetString(1);
            // 如果有大字段,比如第3个是TEXT,用GetChars分块读
            // char[] buffer = new char[1024];
            // long bytesRead = reader.GetChars(2, 0, buffer, 0, buffer.Length);
            
            // 直接处理这条记录,别存集合
            ProcessSingleRecord(id, name);
        }
    }
}

3. 砍掉不必要的内存开销

检查一下你的代码,是不是做了很多浪费内存的操作:

  • 是不是用了SELECT *?只取你需要的字段,能少加载很多不必要的数据。
  • 是不是把所有记录都存到List<T>里了?如果最终是要导出或同步,直接边读边写,别在内存里存副本。
  • 处理字符串的时候是不是频繁创建临时对象?比如用string.Format循环生成字符串,换成StringBuilder能省不少内存。

最后再啰嗦一句

永远不要试图把几十万甚至更多的记录全部加载到内存对象里——不管你内存多大,数据量再涨一点还是会爆。流式处理或者分批读取才是处理大数据的正确姿势,从根源上减少内存占用,而不是靠提升内存阈值续命。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 08:24:29