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
相关产品推荐
相关产品推荐

