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

页面加载大量SQL数据时触发Out of memory 浏览器内存溢出错误

问题描述

批量Excel数据导入SQL数据库后,加载数据展示页面时浏览器提示out of memory错误,数据量约100行时无异常,错误提示如下:
错误提示截图

现有实现存在三处核心问题导致内存溢出:

  • 后端接口写法错误:当前Index接口返回的是_db.Database整个数据库上下文对象,和视图绑定的IEnumerable<Employee>模型不匹配,会携带大量无用冗余数据参与视图渲染,额外占用网络传输和浏览器内存资源。
  • 全量加载逻辑缺陷:当前实现将全量员工数据一次性从数据库查出,在服务端渲染成所有表格行的静态HTML返回给浏览器,DataTable初始化时还要解析全量DOM节点生成内部数据缓存。当数据量达到数千/数万行时,DOM节点数量、JS内存占用会超过浏览器单标签页内存阈值,触发内存溢出;100行数据量小,不会触发阈值因此运行正常。
  • 视图DOM结构不规范:表头仅定义了3列,实际每行渲染了4列(3个数据列+1个操作列),浏览器渲染时会额外做兼容处理,增加不必要的内存开销。
根治方案:启用DataTable服务端分页

核心思路是不再一次性加载、渲染全量数据,每次只拉取当前页需要的少量数据,DOM节点永远只保留当前页内容,从根源上控制内存占用,支持十万级以上数据量无压力。

步骤1:修复后端基础查询逻辑

废弃返回_db.Database的错误写法,新增专门的分页查询接口供DataTable调用,接收分页参数,仅返回当前页需要的数据和总条数:

[HttpGet]
public IActionResult GetEmployeePage(int draw, int start, int length)
{
    // 统计总数据量
    var totalCount = _db.Employees.AsNoTracking().Count();
    // 分页取数,仅查询当前页需要的字段,关闭对象跟踪减少内存占用
    var pageData = _db.Employees
        .OrderBy(e => e.id)
        .Skip(start)
        .Take(length)
        .Select(e => new 
        {
            e.id,
            e.identifier,
            e.firstname,
            e.middlename,
            e.lastname
        })
        .AsNoTracking()
        .ToList();

    // 组装DataTable要求的返回格式,操作列HTML直接在后端拼接
    var result = pageData.Select(e => new 
    {
        e.firstname,
        e.middlename,
        e.lastname,
        operation = $@"
        <div class='form-button-action'> 
            <button type='submit' class='btn btn-link btn-lg'>
                <a href='/Exposure/ViewDetails?Identifier={e.identifier}' class='fas fa-users' data-toggle='tooltip' data-placement='top' title='View Accumulation'></a>
            </button>
            <button type='submit' class='btn btn-link btn-lg' onclick='Edit({e.id})'>
                <a class='fas fa-pencil-alt' data-toggle='tooltip' data-placement='top' title='Edit Session'></a>
            </button>
        </div>"
    });

    return Json(new 
    {
        draw = draw,
        recordsTotal = totalCount,
        recordsFiltered = totalCount,
        data = result
    });
}

如果需要保留原Index页访问入口,修改Index接口逻辑为直接返回视图即可,不需要传全量数据:

public IActionResult Index()
{
    return View();
}

步骤2:修改Razor视图,移除全量行渲染逻辑

删除原来遍历Model渲染所有行的代码,补全表头列,tbody留空交由DataTable动态填充:

@{
    ViewData ["Title"] = "Index";
}
<div class="table-responsive">
    <table class="table text-center table-striped table-hover" id="basic-datatables" cellspacing="1">
        <thead class="bg-dark text-light">
            <tr>
                <th>First Name</th>
                <th>Middle Name</th>
                <th>Last Name</th>
                <th>Operation</th>
            </tr>
        </thead>
        <tbody></tbody>
    </table>
</div>

步骤3:修改前端DataTable配置,开启服务端模式

<script>
var table = $('#basic-datatables').DataTable({
    searching: false,
    processing: true,
    serverSide: true, // 开启服务端分页核心开关
    pageLength: 20, // 每页默认显示条数,可按需调整为10/50/100
    oLanguage: {
        sEmptyTable: "No Record Found"
    },
    ajax: {
        url: '/Employee/GetEmployeePage', // 替换为实际控制器路由
        type: 'GET'
    },
    columns: [
        { data: 'firstname' },
        { data: 'middlename' },
        { data: 'lastname' },
        { data: 'operation', orderable: false } // 操作列关闭排序
    ]
});
</script>
临时过渡方案(数据量<5000行场景)

如果数据量不大,暂时不想重构为服务端分页,可通过两个优化快速解决问题:

  1. 后端查询时关闭EF对象跟踪,仅返回需要的字段,禁止返回_db.Database:
public IActionResult Index()
{
    var employees = _db.Employees
        .Select(e => new Employee
        {
            id = e.id,
            identifier = e.identifier,
            firstname = e.firstname,
            middlename = e.middlename,
            lastname = e.lastname
        })
        .AsNoTracking()
        .ToList();
    return View(employees);
}
  1. DataTable开启延迟渲染,初始化时仅创建当前页的DOM节点,不一次性生成所有行的DOM:
var table = $('#basic-datatables').DataTable({
    searching: false,
    deferRender: true, // 延迟渲染开关
    pageLength: 20,
    oLanguage: {
        sEmptyTable: "No Record Found"
    }
});

注意:该方案仅支持5000行以内数据,数据量更大时必须使用服务端分页方案。

额外优化点
  • 原代码中所有编辑按钮都使用了相同的id="Edit",不符合HTML id唯一的规范,会导致JS事件异常,建议移除重复id,用class选择器绑定事件。
  • 按钮嵌套<a>标签的写法不符合HTML规范,建议直接用<a>标签做跳转,不需要套一层button。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 10:00:54