DuckDB 数据导入管线:元数据只保留 zip 级别,导入先预检查再处理
背景
zdq 项目(d:\code\python\zdq)的行情数据导入管线出现 15 分钟股票/指数数据为 0、导入脚本卡死、日周月脚本重复全量处理等一系列问题。本条目沉淀这次修复中确认的设计原则与排障方法。
核心内容
1. 元数据表只记 zip 级别,不记 zip 内部 csv(核心教训)
原 build_data_meta.py 为每个 zip 内的每个 csv 都建一条元数据记录(路径形如 xxx.zip#sh600000.csv),后果:
- 15 分钟数据产生 1561 万条 csv 级记录,全库达 7789 万条
- 查询待导入文件扫描全表,单次 41 秒;
UPDATE元数据全表扫描,每批次提交 3-4 分钟 - csv 记录的
is_imported永远不会被更新,脚本每次运行都重复匹配 → 重复导入
改为只保留 zip 级别后,15 分钟元数据从 1561 万条降到约 1.2 万条,查询/更新走主键毫秒级完成。原则:元数据粒度 = 实际导入处理的最小单元;zip 按整个文件导入,就只记 zip。
2. 用重建表代替大表 DELETE
清理 7790 万条冗余元数据时,重建表(复制有效记录建新表再替换)仅 3.8 秒,远快于 DELETE。DuckDB/列存场景下,删除大比例行优先用重建。
3. 幂等导入靠主键约束兜底
(trade_time, ts_code) 主键 + INSERT ... ON CONFLICT DO NOTHING 保证脚本重复运行不产生重复数据。验证方法:总行数 == 唯一键组合数。但这只保证正确性,不保证效率。
4. 导入前先预检查,避免"读取→拼接→注册→被主键拒绝"的空转
import_daily_weekly_monthly.py 原逻辑每次全量遍历 6059 个股票目录、读取所有 csv、拼 DataFrame,最后被主键全部拒绝。优化为先查目标表数据量/日期范围,已导入直接跳过,耗时从数十分钟降到 2 秒。
5. 元数据与导入脚本必须指向同一个库、同一套路径格式
这次事故的多个环节都源于不一致:build_data_meta.py 指向 data_meta.duckdb(F盘)而导入脚本用 stock_data.duckdb;元数据路径格式(csv 结尾)与导入脚本过滤条件(LIKE '%.zip')不匹配,导致 1561 万条记录全部漏导。改库名、改路径格式时,元数据生成端和消费端必须一起改。
6. 数据目录约定(zdq 项目)
指数日月周K线数、A股沪深日月周K线数:csv 数据(指数原包为 zip,解压后有_temp目录需过滤)A股_分时数据_沪深、A股_分时数据_指数:zip(按月归档),不要管内部 csvbuild_data_meta.py扫描时须过滤_temp临时目录
7. 排障方法要点
- 脚本"卡死"不一定是卡死:本次真实原因是每批次后的元数据 UPDATE 扫描 1561 万条记录耗时 3-4 分钟,插入本身 12-13 万条/秒。先分层测试(读 zip → 插内存表 → 插磁盘表 → 元数据更新)定位真正慢的环节,别急着改主流程。
- 插入性能参考:DuckDB 单表批量插入 100 万行约 1.44 秒,分钟级行情导入 13-21 万条/秒属正常。
原文链接
提炼自 inbox/trae/20260424_修复并导入15分钟数据.md
