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

.NET Core 6向126列表插入650万条记录的内存溢出问题求解

解决大批量数据插入的内存溢出与性能问题

核心问题分析

当前代码的内存压力主要来自:

  • 一次性加载整个.cat文件到List<string> lines,650万行直接占用大量内存
  • 使用AddRange让DbContext跟踪实体,额外消耗内存
  • 15万条的批量大小过大,单批次实体(126列)占用内存超出容器限制
  • 容器未配置内存请求/限制,触发OOM被系统强制重启

具体优化方案

1. 流式读取文件,避免一次性加载全量数据

放弃ReadAllLines一次性加载文件的方式,改用File.ReadLines流式逐行读取,从根源降低初始内存占用:

// 替换原方法的List<string> lines参数,改为接收文件路径
public void InsertCatsFromFileWithBulkInsertAzure(string filePath, string name, string username, Guid id, DateTime? dateCat)
{
    DateTime now = DateTime.Now;
    int batchSize = 30000; // 调小批量大小,建议2-5万,根据内存情况调整
    List<Cat> data = new List<Cat>(batchSize); // 初始化时指定容量,减少内存分配次数

    var executionStrategy = _context.Database.CreateExecutionStrategy();
    executionStrategy.Execute(() =>
    {
        foreach (var line in File.ReadLines(filePath).Skip(1)) // Skip(1)跳过表头行
        {
            try
            {
                Cat parsedCat = ParseCat(line); // 调整ParseCat方法,直接接收单条line参数
                parsedCat.CatGeneralId = id;
                parsedCat.Date = now;
                parsedCat.DateCat = dateCat;
                data.Add(parsedCat);

                if (data.Count == batchSize)
                {
                    // 用BulkInsert替代AddRange+BulkSaveChanges,直接跳过实体跟踪
                    _context.BulkInsert(data, options =>
                    {
                        options.BatchSize = batchSize;
                        options.DisableTracking = true; // 禁用实体跟踪,节省内存
                        options.EnableStreaming = true; // 流式写入数据库,减少内存占用
                    });
                    data.Clear();
                    data.TrimExcess(); // 释放列表多余的内存空间
                }
            }
            catch (Exception ex)
            {
                string type = line.Substring(0, 2);
                string cadastralParcel = line.Substring(30, 14);
                Console.WriteLine($"错误:地籍编号{cadastralParcel},类型{type},错误信息:{ex.Message}");
            }
        }

        // 处理剩余不足一批的数据
        if (data.Count > 0)
        {
            _context.BulkInsert(data, options =>
            {
                options.DisableTracking = true;
                options.EnableStreaming = true;
            });
        }
    });
}

2. 优化DbContext生命周期

避免长期持有同一个DbContext实例,每处理N批后重建DbContext,防止其内部积累状态占用内存:

// 在批量插入逻辑中加入DbContext重置逻辑
int batchCounter = 0;
int resetInterval = 5; // 每5批重置一次DbContext

if (data.Count == batchSize)
{
    _context.BulkInsert(/*...配置参数...*/);
    data.Clear();
    data.TrimExcess();
    batchCounter++;
    
    if (batchCounter >= resetInterval)
    {
        _context.Dispose();
        _context = new YourDbContext(); // 重新创建DbContext实例
        batchCounter = 0;
    }
}

3. 数据库层面提速优化

插入前临时禁用非核心约束和索引,插入完成后重建,大幅提升插入速度:

-- 插入前执行:禁用非聚集索引和外键约束
ALTER INDEX ALL ON Cats DISABLE;
ALTER TABLE Cats NOCHECK CONSTRAINT ALL;

-- 执行完所有插入操作后执行:重建索引并启用约束
ALTER INDEX ALL ON Cats REBUILD;
ALTER TABLE Cats CHECK CONSTRAINT ALL;

4. 容器资源配置

给容器设置合理的内存请求和限制,避免系统因OOM强制重启:

  • Docker Compose示例:
    services:
      fifi-web:
        image: your-image
        deploy:
          resources:
            limits:
              memory: 8G # 根据实际需求调整,比如8G或16G
            reservations:
              memory: 4G # 内存请求,确保调度器分配足够资源
    
  • Azure App Service:在应用服务的「配置-常规设置」中调整内存配额

额外注意事项

  • 内存监控:在代码中加入内存日志,比如Console.WriteLine($"当前内存占用:{GC.GetTotalMemory(false) / 1024 / 1024} MB");,方便调整批量大小
  • 错误记录:将错误信息写入日志系统而非仅输出控制台,方便后续排查
  • 小批量测试:先测试10万、50万条数据,验证内存占用和插入速度后再放大到650万条

内容的提问来源于stack exchange,提问作者Jeffrey Andres Guette Sanguino

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 09:00:48