必须流式读取+分批插入,因fs.readfile会全量加载导致oom或超时;csv-parser需设highwatermark、跳bom、用连接池事务分批提交,避免单次sql过大或连接泄漏。

直接用 fs.readFile 读完整个上亿行 CSV 再塞进 MySQL,十有八九会 OOM 或连接超时。真正可行的路径是:流式读取 + 分批插入 + 连接池复用 + 错误回退控制。
为什么不能一次性 loadAll 再 insertMany?
Node.js 默认堆内存上限约 1.4GB(64位),而 1 亿行、每行 200 字节的 CSV 就接近 20GB 原始体积;即使压缩后用 readFileSync 加载,也会触发 V8 堆溢出或阻塞事件循环,MySQL 连接还可能因等待太久被服务端 kill。
-
fs.readFile和readFileSync都会把整个文件载入内存,不适用于 >10MB 的 CSV -
mysql2的execute或query不支持“自动分片”,传入百万级数组仍会拼成单条 SQL,触发 max_allowed_packet 限制(默认 4MB) - 未配置连接池时,高频
createConnection会导致 TIME_WAIT 爆满,MySQL 报错Too many connections
用 fs.createReadStream + csv-parser 实现可控流式读取
核心是让数据“边读边处理”,内存占用稳定在几 MB 级别,且可随时暂停、跳过脏行。
MySQL 9.6.0是面向Linux平台的2026年创新版本,核心架构迎来重大革新。其将外键约束与级联操作从InnoDB引擎层上移至SQL层,确保所有数据变更均被完整记录至Binlog,彻底解决了CDC(变更数据捕获)与主从复制中的数据不一致难题。此外,该版本引入container_aware启动选项以原生适配容器环境,并对审计日志进行了组件化重构,为追求极致数据一致性与云原生体验的开发者提供了全新选择。
- 安装轻量解析器:
npm install csv-parser(比papaparse更适合 Node 后端流场景) - 必须设置
highWaterMark: 64 * 1024(64KB),否则默认 16KB 可能导致小字段 CSV 解析错位 - 用
pipe链式传递,避免on('data')手动堆积 Promise —— 容易失控 - 示例关键片段:
const fs = require('fs');
const csv = require('csv-parser');
const mysql = require('mysql2/promise');
<p>const pool = mysql.createPool({ host: 'localhost', user: 'root', database: 'test', waitForConnections: true, connectionLimit: 10 });</p><p>fs.createReadStream('huge.csv', { highWaterMark: 64 * 1024 })
.pipe(csv({ headers: true, skipEmptyLines: true }))
.on('data', async (row) => {
try {
await pool.execute('INSERT INTO orders SET ?', row);
} catch (err) {
console.error('skip bad row:', row, err.message);
}
});</p>
分批提交 + 事务边界必须手动控制
单纯靠流式读取还不够——每行都发一次 execute,网络往返开销和事务日志写入会拖垮性能。必须聚合为 1000–5000 行一批,并显式使用事务。
- 不要依赖
INSERT ... VALUES (...),(...),(...)自动批处理:字段类型不一致或 NULL 处理容易出错 - 用
pool.execute('INSERT INTO t(col1,col2) VALUES ? ', [rows]),rows是二维数组,如[[v1,v2],[v3,v4]] - 每批执行前
const conn = await pool.getConnection(),结束后conn.release(),避免连接泄漏 - 遇到错误时,整批 rollback,但记录 offset 继续下一批(不要 throw 中断整个流)
容易被忽略的底层细节
真正卡住进度的往往不是代码逻辑,而是 MySQL 服务端配置和文件权限。
-
LOAD DATA INFILE虽快,但要求 CSV 文件在 MySQL 服务端磁盘上,VSCode 本地开发时需先scp或挂载卷 —— 本地调试阶段不现实 - MySQL 的
max_allowed_packet必须 ≥ 单批最大 SQL 长度,建议设为 64M;同时检查客户端连接参数是否带bigPacket: true - CSV 文件若含 BOM(\uFEFF),
csv-parser默认无法跳过,需用fs.createReadStream(path, { encoding: 'utf8' })并手动截掉头三个字节 - Windows 下路径反斜杠易导致
ENOENT,统一用path.join(__dirname, 'data.csv')










