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
相关产品推荐
相关产品推荐

