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

Node.js导出MySQL 500万数据时出现JavaScript堆内存不足问题求助

解决Node.js导出500万条MySQL数据时的内存溢出问题

问题根源

你代码里的核心问题不是ExcelJS的流没用对,而是MySQL查询会一次性把全表数据加载到内存——哪怕用了Excel的流写入,500万条数据先全堆在内存里,必然触发heap out of memory。另外你用forEach嵌套异步查询,会同时发起多个表的查询,进一步加剧内存占用。

解决方案步骤

1. 启用MySQL的流式查询

不要用con.query()一次性拉取所有数据,改用con.query(sql).stream(),把数据分成小块读取,避免一次性加载全量数据到内存。

2. 串行处理每个发送者的表

用for...of替代forEach,确保处理完一个表的所有数据后,再处理下一个,避免并发查询导致内存暴涨。

3. 配合ExcelJS的流写入

在数据库流的data事件里,逐条写入Excel工作表,写完就提交释放内存,不要缓存所有行数据。

4. 移除不必要的内存复制

删掉var mysqlData = JSON.parse(JSON.stringify(data));,这会额外复制一份数据,浪费内存。

修改后的完整代码

exports.exportdatatocsv = async (req, res) => {
  try {
    // 获取所有发送者和对应表名(该数据量小,用普通query没问题)
    const [senderList] = await con.promise().query(
      "SELECT sender_name, table_name FROM sender_tbl WHERE sender_name IS NOT NULL"
    );

    // 初始化Excel流式写入器,关闭非必要功能减少内存占用
    const workbook = new ExcelJS.stream.xlsx.WorkbookWriter({ 
      stream: res,
      useStyles: false,
      useSharedStrings: false
    });

    // 设置响应头,让浏览器识别为可下载的Excel文件
    res.setHeader(
      "Content-Type",
      "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    );
    res.setHeader(
      "Content-Disposition",
      `attachment; filename="export_${Date.now()}.xlsx"`
    );

    // 串行处理每个发送者的表
    for (const sender of senderList) {
      const worksheet = workbook.addWorksheet(
        sender.sender_name.substring(0, 31) // 工作表名限制31字符
      );

      // 写入表头并立即提交
      const fileHeader = ["message", "info", "credit/debit", "amount", "netbal"];
      await worksheet.addRow(fileHeader).commit();

      // 启用MySQL流式查询,只查询需要的字段减少数据传输
      const queryStream = con.query(
        `SELECT message, info, isdebit, amount, netbal FROM ${sender.table_name} ORDER BY id DESC`
      ).stream();

      // 监听流事件,逐条处理数据
      await new Promise((resolve, reject) => {
        queryStream
          .on("data", async (row) => {
            queryStream.pause(); // 暂停流避免写入过快积压内存
            
            // 转换数据并写入行,写完立即提交
            await worksheet.addRow([
              row.message,
              row.info,
              row.isdebit ? "debit" : "credit",
              row.amount,
              row.netbal
            ]).commit();

            queryStream.resume(); // 恢复流继续读取下一批数据
          })
          .on("end", resolve)
          .on("error", (err) => {
            console.error(err);
            reject(err);
          });
      });
    }

    // 所有表处理完成后提交工作簿
    await workbook.commit();
    res.status(200).end();
  } catch (error) {
    console.error(error);
    res.status(500).send("Internal Server Error");
  }
};

额外优化建议

  • 启动Node.js时增加内存上限:添加参数--max-old-space-size=8192(分配8G内存,根据服务器配置调整)
  • 确保MySQL连接配置了合理的highWaterMark,控制每次读取的数据块大小
  • 避免在查询中用SELECT *,只查询需要的字段减少数据传输量

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 11:31:08