DuckDB 大批量数据导入的性能劣化诊断与优化方法
背景
量化项目向 DuckDB 导入数千万个分钟线 CSV/zip 文件(目标量级数亿行)时,导入任务出现典型的"越跑越慢、内存越涨越高"现象:速度从 18.5 万条/秒跌到 13.7 万条/秒(-26%),内存从 2.7GB 涨到 6.7GB(+148%)。对话中定位了原因并验证了一套优化方案,优化后速度回到 19.6 万条/秒、内存稳定在 2.7-3.3GB。以下是可复用的方法论。
核心内容
性能劣化的四个根因
- DuckDB 临时视图泄漏(内存主因):每次插入前
con.register("tmp", df)注册视图,但从不unregister。Python 侧del df不会清理 DuckDB 连接内的引用,临时表引用持续累积。 - 大表主键冲突检查成本递增(速度主因):目标表已有 16.7 亿行,
INSERT ... ON CONFLICT DO NOTHING的唯一性检查成本随表体积增长,越到后期越慢。 - 事务粒度过小:每 3 个文件就
commit+ 逐条UPDATE元数据表,频繁触发 WAL 写入和锁竞争。 - 资源句柄泄漏风险:
zipfile等句柄在异常路径下可能未释放。
优化方案(实测有效)
| 优化点 | 做法 | 效果 |
|---|---|---|
| 临时表清理 | 用 CREATE TEMP TABLE + 用完即 DROP TABLE IF EXISTS,替代 register/unregister |
切断内存泄漏源 |
| 事务粒度 | 按月份统一 COMMIT,元数据月份结束批量 UPDATE | 减少 WAL 开销与锁竞争 |
| 定期 CHECKPOINT | 每 10 批次执行一次,强制释放 WAL 和内存 | 内存曲线由线性增长变为可控波动 |
| 句柄管理 | try/finally 显式 close() |
防止异常路径泄漏 |
| 批次调大 | 每批 3 万行/3 文件 → 5 万行/5 文件 | 减少事务次数 |
实测对比:速度 +43%(13.7 万 → 19.6 万条/秒),内存 -51%(6.7GB → 3.3GB),且不再线性膨胀。
配套经验
- 长时任务必须有可观测性:按月份打日志(速度+内存),劣化趋势一眼可见,这是能定位问题的前提。
- 断点续传是长时导入的刚需:元数据表记录已导入文件,任务可随时中断重启,也让"先停掉旧脚本、换优化版再跑"零成本。
- 文件分组正则要对脏数据健壮:归档路径格式不一(
2020/01/、202001/、/1/、按年汇总 zip)时,过严的正则会把文件漏进"其他"分组;解析失败要显式分类而不是静默吞掉。 - 重复数据源先做减法:按年汇总与按月归档是同一份数据,直接过滤掉按年文件,待导入量立刻减少(本例 1302 → 1262 个文件)。
原文链接
提炼自 inbox/trae/20260424_测试模型调用.md
