使用Mongoose向MongoDB插入百万级CSV数据时JS堆内存溢出如何解决
问题根因
你前两次的实现均将全量300万条过滤后的记录全部缓存到了内存中的results数组,Node.js默认堆内存上限仅为1~4GB(随版本、系统架构变化),完全无法承载如此大的数据集,因此不管后续是否做分批插入,都会触发堆内存溢出。
最优实现方案
核心思路是基于流的背压控制,边读取CSV边批量插入数据库,内存中仅保留当前待插入的批次数据,不缓存全量数据,完全规避全量数据加载到内存的问题。
实现代码
const fs = require('fs'); const csv = require('csv-parser'); const mongoose = require('mongoose'); // 替换为你实际定义的PhotoModel路径 const PhotoModel = require('./models/photo'); async function importCsvToMongo() { // 批次大小可根据服务器配置调整,建议范围500~2000 const BATCH_SIZE = 1000; let currentBatch = []; let stream; return new Promise((resolve, reject) => { stream = fs.createReadStream('myFile.csv') .pipe(csv()) .on('data', async (data) => { // 过滤id为48的记录 if (data.id !== '48') { currentBatch.push(data); } // 攒够批次大小后暂停读流,插入完成后再恢复 if (currentBatch.length >= BATCH_SIZE) { stream.pause(); try { // ordered: false表示单条插入失败不阻断同批次其他记录插入 await PhotoModel.insertMany(currentBatch, { ordered: false }); currentBatch = []; stream.resume(); } catch (err) { reject(err); } } }) .on('end', async () => { // 插入最后不足一批的剩余数据 if (currentBatch.length > 0) { try { await PhotoModel.insertMany(currentBatch, { ordered: false }); } catch (err) { reject(err); return; } } console.log('全部数据导入成功'); resolve(); }) .on('error', (err) => { reject(err); }); }); } // 启动导入,替换为你的MongoDB连接地址 mongoose.connect('mongodb://localhost:27017/你的数据库名') .then(() => importCsvToMongo()) .then(() => mongoose.disconnect()) .catch(err => { console.error('导入失败:', err); mongoose.disconnect(); });
优化建议
- 导入前可先删除集合的非必要索引,待导入完成后再重建,可大幅提升导入速度
- 若存在重复主键等冲突场景,可将
insertMany替换为批量updateOne搭配upsert: true参数处理冲突 - 特殊场景下如果需要进一步提升导入速度,可适当调大
BATCH_SIZE,或执行脚本时添加参数node --max-old-space-size=8192 脚本文件名.js临时提升Node.js堆内存上限
内容的提问来源于stack exchange,提问作者h_a
相关产品推荐
相关产品推荐

