You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Knex.js内存优化:百万级CSV数据导入SQLite的高效方案

优化200万行CSV导入SQLite的内存效率与并发问题

你的原始代码会把200万行数据全部加载到内存中,这会导致极高的内存占用,而且一次性批量插入也可能触发SQLite的性能瓶颈。之前在data回调中直接调用插入出现错误,本质是流式读取的速度远快于数据库插入速度,导致大量并发插入请求堆积——而SQLite是单写数据库,无法处理并行写入操作,进而引发锁冲突或超时错误。

下面是内存高效且避免并发问题的优化实现:

async function insertData(filePath: string, tableName: string) {
  const BATCH_SIZE = 1000; // 可根据SQLite性能调整,建议500-2000之间
  let batch: CsvRow[] = [];
  let streamPaused = false;

  await new Promise<void>((resolve, reject) => {
    const stream = fs.createReadStream(filePath)
      .pipe(fastcsv.parse({ headers: true }))
      .on("data", async (row: CsvRow) => {
        if (streamPaused) return;
        
        batch.push(row);
        // 攒够批次就开始插入
        if (batch.length >= BATCH_SIZE) {
          stream.pause();
          streamPaused = true;
          try {
            await db.batchInsert(tableName, batch, BATCH_SIZE);
            console.log(`Inserted ${BATCH_SIZE} rows`);
            batch = []; // 清空批次
            streamPaused = false;
            stream.resume(); // 恢复流读取
          } catch (error) {
            console.error("Batch insert failed:", error);
            stream.destroy(); // 终止流
            reject(error);
          }
        }
      })
      .on("end", async () => {
        // 插入剩余不足一个批次的行
        if (batch.length > 0) {
          try {
            await db.batchInsert(tableName, batch, batch.length);
            console.log(`Inserted remaining ${batch.length} rows`);
          } catch (error) {
            console.error("Final batch insert failed:", error);
            reject(error);
            return;
          }
        }
        console.log("All data inserted successfully");
        resolve();
      })
      .on("error", (error) => {
        console.error("Stream error:", error);
        reject(error);
      });
  });
}

关键优化点

  • 流式分批处理:不再一次性加载所有数据,内存仅保留当前批次的行,内存占用大幅降低
  • 串行批次插入:每批次插入完成后再读取下一批,彻底避免SQLite的并发写入冲突
  • 流控(pause/resume):防止流的读取速度超过数据库插入速度,避免内存溢出

额外性能优化建议

  • 调整批次大小:如果SQLite写入慢,可适当减小批次;内存允许的话,增大批次能减少数据库交互次数,提升整体速度
  • 开启SQLite WAL模式:预写日志模式支持更高的并发读写性能,能显著提升批量插入速度:
    await db.raw("PRAGMA journal_mode = WAL;");
    
  • 临时关闭约束与索引:插入前关闭外键检查和索引,插入完成后恢复,避免每次插入都更新索引的开销:
    // 插入前执行
    await db.raw("PRAGMA foreign_keys = OFF;");
    // 插入完成后恢复
    await db.raw("PRAGMA foreign_keys = ON;");
    

内容的提问来源于stack exchange,提问作者Wes Kim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 15:00:26