Node.js如何将MySQL千万级大数据集分块有序写入CSV文件
Node.js 分批次导出MySQL大表到CSV避免内容错乱的解决方案
问题核心原因
你当前的storeFile是异步执行逻辑,没有等待当前批次数据完全写入磁盘,就触发了下一批次的查询和写入操作,多个并行的写入流同时向同一个文件追加内容,就会出现行内容混杂、记录无序的问题。
代码改造方案
第一步:改造storeFile为返回Promise的异步方法
将写入完成/失败的回调逻辑封装为Promise,方便上层用await控制执行时序:
const storeFile = (filename, headers, jsonData, append) => { return new Promise((resolve, reject) => { const flags = append === true ? 'a' : 'w' const ws = fs.createWriteStream(filename, { flags }) fastcsv .write(jsonData, { headers, rowDelimiter: '\r\n' }) .on('error', (err) => { logger.error(`csv格式化失败: ${err.message}`) ws.destroy() reject(err) }) .pipe(ws) // 监听写入流完成事件 ws.on('finish', () => { logger.info(`批次写入完成,文件: ${filename}`) resolve() }) // 监听写入流错误事件 ws.on('error', (err) => { logger.error(`文件写入失败: ${err.message}`) reject(err) }) }) }
第二步:主循环增加await等待写入完成
每次查询到批次数据后,等待当前批次完全写入完成,再更新offset、执行下一轮查询:
// 导出逻辑需要放在async函数内 const exportLargeTableToCsv = async () => { const filename = 'test.csv' let offset = 0 let append = false let writeHeaders = true const limit = 20000 // 建议动态查询表总条数,不要硬编码 const totalCount = await Record.count() const totalIterations = Math.ceil(totalCount / limit) for (let i = 0; i < totalIterations; i++) { const records = await Record.findAll({ offset, limit, raw: true }) // 等待当前批次写入完成再继续后续逻辑 await storeFile(filename, writeHeaders, records, append) // 首次写入后关闭表头写入、开启追加模式 writeHeaders = false append = true offset += limit // 可选:打印导出进度 logger.info(`导出进度: ${((i + 1) / totalIterations * 100).toFixed(2)}%`) } logger.info('全量数据导出完成') } // 执行导出,捕获全局异常 exportLargeTableToCsv().catch(err => { logger.error(`导出任务失败: ${err.stack}`) })
可选优化建议
- 可全程复用同一个文件写入流和fastcsv实例,避免每次批次都打开/关闭文件的开销,性能更好。
- 1000万条数据使用offset分页查询,越到后段查询性能越差,建议改用主键排序分页:每次查询时记录当前批次最大的主键ID,下一次查询用
where id > 上次最大ID limit 20000的条件查询,性能远高于offset分页。
内容的提问来源于stack exchange,提问作者fpelaezt
相关产品推荐
相关产品推荐

