Node.js导入超大JSON文件至MongoDB报错解决方案
实现方案
原有代码报错原因
- 异步逻辑缺失:
newLink.save()为异步操作,循环中未等待写入完成就发起下一次请求,75万条无并发控制的请求会直接打满数据库连接,甚至在库、集合未完成初始化时就发起写入,触发建库失败、连接超时问题。 - 数据类型不匹配:通过
require()加载JSON文件得到的是完整URL数组,并非单条字符串,直接将数组赋值给link字段不符合Schema字段类型要求,必然触发校验错误。 - 写入效率极低:单条循环调用
save()的网络IO开销是批量写入的上百倍,75万条数据量级下极易中途出现内存溢出、连接断连问题。
前置配置
首先定义Schema并给URL字段添加唯一索引,从根源避免重复数据,同时大幅提升后续查询、校验速度:
const mongoose = require('mongoose'); const fs = require('fs'); const path = require('path'); // 定义诈骗URL集合Schema const ScamLinkSchema = new mongoose.Schema({ link: { type: String, required: true, unique: true, trim: true }, createdAt: { type: Date, default: Date.now } }); const Rescamdb = mongoose.model('rescamdb', ScamLinkSchema);
75万条URL高效导入实现
采用分批插入策略,单批大小控制在2000条,避免超过MongoDB单文档16M大小限制,同时通过ordered:false配置跳过重复数据,不中断整体导入流程:
async function batchImportScamLinks() { // 等待数据库连接成功后再执行后续操作 await mongoose.connect('mongodb://127.0.0.1:27017/你的目标库名', { maxPoolSize: 20 // 控制连接池大小,避免连接数溢出 }); console.log('数据库连接成功,初始化索引'); // 提前创建索引,完成后再导数据 await Rescamdb.init(); const files = fs.readdirSync(path.join(__dirname, './files')); for (const file of files) { // 用fs读取JSON替代require,避免require缓存问题、大文件加载异常 const fileContent = fs.readFileSync(path.join(__dirname, './files', file), 'utf8'); const linkArr = JSON.parse(fileContent); console.log(`加载文件${file},共读取${linkArr.length}条URL,开始分批导入`); const batchSize = 2000; for (let i = 0; i < linkArr.length; i += batchSize) { // 组装当前批次数据 const currentBatch = linkArr.slice(i, i + batchSize).map(link => ({ link: link.trim() })); try { await Rescamdb.insertMany(currentBatch, { ordered: false }); console.log(`导入进度:${Math.min(i + batchSize, linkArr.length)}/${linkArr.length}`); } catch (err) { // 仅忽略重复键错误,其余错误打印排查 if (err.code !== 11000) console.error(`批次${i}导入异常`, err); } } } console.log('全部数据导入完成'); } batchImportScamLinks().catch(err => { console.error('导入任务失败', err); process.exit(1); });
提示:如果单份JSON文件大小超过100M,替换为流式JSON解析工具做逐段读取即可,75万条纯URL总大小通常在15M左右,直接读取不会有内存压力。
跨库URL不匹配校验实现
采用Set做内存比对避免频繁查库,分批拉取业务库数据控制内存占用,校验速度比单条查询快100倍以上:
async function verifyUrlMatch() { // 分别连接两个目标数据库 const scamDbConn = mongoose.createConnection('mongodb://127.0.0.1:27017/诈骗URL存储库'); const bizDbConn = mongoose.createConnection('mongodb://127.0.0.1:27017/待校验业务库'); const ScamLinkModel = scamDbConn.model('rescamdb', ScamLinkSchema); // 替换为待校验库的集合结构 const BizLinkModel = bizDbConn.model('你的业务集合名', new mongoose.Schema({ url: { type: String, trim: true } })); // 仅拉取URL字段,减少不必要的内存开销 const allScamLinks = await ScamLinkModel.find({}, { link: 1, _id: 0 }).lean(); const scamLinkSet = new Set(allScamLinks.map(item => item.link)); const batchSize = 5000; let offset = 0; const mismatchResult = []; while (true) { // 分批拉取业务库URL const bizLinks = await BizLinkModel.find({}, { url: 1, _id: 0 }) .skip(offset) .limit(batchSize) .lean(); if (bizLinks.length === 0) break; for (const item of bizLinks) { // 比对逻辑可按实际需求调整:以下逻辑为业务库URL不在诈骗库中则判定为不匹配 if (item.url && !scamLinkSet.has(item.url)) { mismatchResult.push(item.url); } } offset += batchSize; console.log(`校验进度:已完成${offset}条数据检查`); } console.log(`校验完成,共发现${mismatchResult.length}条不匹配URL`); // 可按需将mismatchResult写入文件或存储到指定集合 } // 导入完成后再执行校验 // batchImportScamLinks().then(() => verifyUrlMatch());
性能注意事项
- 所有数据库异步操作必须加
await控制流程,禁止无限制发起并发请求 - 导入、校验前必须完成URL字段的索引创建,查询、写入速度可提升两个数量级
- 批量操作单批数据量控制在1000-5000条,避免单次请求数据量过大触发MongoDB限制
- 大数量查询使用
lean()方法,跳过Mongoose文档实例化过程,可减少60%以上内存开销 - 导入前提前对URL做去重、去空格处理,避免脏数据导致校验结果偏差
内容的提问来源于stack exchange,提问作者Peter
相关产品推荐
相关产品推荐

