You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何通过NodeJS API导出MongoDB全量集合数据为CSV(内存溢出问题)

解决MongoDB全量导出CSV时Node.js堆内存溢出问题

这个问题太典型了——一次性把50多万条文档全加载到内存里,直接把Node.js的堆内存给撑爆了。咱们换个思路,用流式处理分批读取数据,不用一次性把所有数据都存到内存里,就能完美解决溢出问题。

问题根源

你的原代码里Collection.find({}).then(logs => ...)会让MongoDB驱动把所有查询结果一次性转换成JavaScript对象数组,全部塞进Node.js的堆内存。50万条数据的内存占用远超Node.js默认的堆内存限制(通常在1.4GB左右),自然触发了内存溢出错误。

解决方案:游标+流式处理

MongoDB驱动支持游标(cursor),可以分批从数据库拉取数据,配合Node.js的流API,我们能做到边读数据、边转CSV、边发响应,全程不会把所有数据都存到内存里。

修改后的代码示例:

const { Readable } = require('stream');

// 先设置响应头(和原来逻辑一致)
res.charset = 'utf-8';
res.setHeader('Content-disposition', 'attachment; filename=filename.csv');
res.setHeader('content-type', 'text/csv');

// 获取MongoDB游标,替代一次性拉取全量数据
const cursor = Collection.find({}).cursor();

// 将游标转换为可读流(部分MongoDB驱动版本可直接用cursor作为流,这里兼容处理)
const dataStream = Readable.from(cursor);

// 配置CSV转换规则,保留你的字段映射逻辑
const csvStream = csv.format({ 
  headers: true,
  transform: function(row) {
    // 注意处理可能的null/undefined字段,避免转换报错
    return {
      'Nivel': row.level || '',
      'Metodo': row.method || '',
      'IP': row.ip || '',
      'URL': row.url || '',
      'Status Code': row['status-code'] || '',
      'Usuario': row.user_email?.email || '', // 用可选链处理嵌套字段的空值
      'Fecha': row.createdAt || '',
      'Cuerpo': row.req_body || '',
      'Consulta': row.req_query || '',
      'Navegador': row['user-agent'] || '',
      'Datos Usuario': JSON.stringify(row.user_email) || '', // 对象转字符串避免CSV格式混乱
      'Aplicacion': row.referrer || ''
    };
  }
});

// 连接流管道:游标数据流 → CSV转换流 → 响应输出流
dataStream.pipe(csvStream).pipe(res);

// 处理流的异常情况
dataStream.on('error', (err) => {
  console.error('读取MongoDB数据出错:', err);
  res.status(500).send('CSV导出失败');
});

csvStream.on('error', (err) => {
  console.error('CSV格式转换出错:', err);
  res.status(500).send('CSV导出失败');
});

res.on('finish', () => {
  console.log('全量数据CSV导出完成');
});

额外优化建议

  1. 自定义分批大小:可以给游标设置batchSize参数,控制每次从数据库拉取的文档数量,比如Collection.find({}).cursor({ batchSize: 2000 }),默认是1000,你可以根据服务器内存情况调整。
  2. 字段过滤:如果不需要导出所有字段,在find()里指定需要的字段,减少单条文档的内存占用,比如Collection.find({}, { level: 1, method: 1, ip: 1 })
  3. 临时内存扩容(非首选):如果只是临时应急,可以启动Node.js时增加堆内存限制,比如node --max-old-space-size=4096 your-api-script.js(设置4GB堆内存),但这只是治标不治本,流式处理才是长期解决方案。

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

火山引擎 最新活动