【Python量化系统工程化 #01】5000只股票取一次就要存半年?四种存储格式横向对比
痛点开场
"我这脚本今天终于跑出第一份回测了!"——你兴奋地把 10 只股票、250 天的 K 线落盘,准备明天再跑一次。然后你打开了"脚本输出"文件夹,看到:
- klines.csv:177 KB
- klines.db:324 KB
- klines.parquet:104 KB
"……我该用哪个?明天再跑是不是要换个格式?同事打开 klines.csv 说"为啥里面有 2500 行我看不到之前那 200 行"……"
事实上,当你把"我取了一次数据"扩展成"我每天取一次、每天分析一次、每次给团队里 5 个同事查一次",存储格式的选择就从一个"无所谓"变成一个"省 10 倍磁盘 / 100 倍查询时间"的工程决策。
本文就把这个决策讲透——四种主流格式(CSV / SQLite / Parquet / MySQL)的真实对比,含一份能跑的代码、一个能复用的方法论。
本文你将得到什么
- 一份 2500 行 K 线的实测数据,对比四种存储的体积、写入速度、查询速度、接口复杂度
- 选型决策树:什么场景用 CSV、什么用 SQLite、什么用 Parquet、什么用 MySQL
- 一个能直接抄的代码模板:四种格式统一调用接口
一、四种存储格式是什么
- CSV(最朴素)
文本文件,每行一条记录,逗号分隔。Excel 双击就能开。
code,t,o,h,l,c,v,a,pc
000001.SZ,2025-08-06,11.5,11.6,11.3,11.55,1234567,14234567,11.4
...
好处:人眼能读、Excel 能开、所有工具都支持。
坏处:查询要全表扫描(2500 行读 30 ms,250 万行就难说了 12 秒);不支持类型(数字可能被读成字符串);每次重写都会产生新文件。
- SQLite(嵌入式数据库)
一个文件 = 一个完整的 SQL 数据库。Python 标准库自带 sqlite3。
SELECT AVG(c) FROM klines WHERE code = '600519.SH'; -- 0.3 ms 跑完
好处:B+ 树索引 = 查询 100 倍提速;事务原子性;单进程 ≤1000 万行毫无压力。
坏处:并发写会锁库(SQLite 设计上就是单写者);不跨机器;不适合几十 GB。
- Parquet(列存文件)
二进制列存文件,按列压缩、查询时只读需要的列。pyarrow / DuckDB / Spark 都能读。
好处:体积最小(实测 2500 行从 177 KB → 104 KB,按量压缩更好);只读两列时磁盘 IO 极低。
坏处:人眼不可读(除非 pandas.read_parquet 后 print(df.head()));写比 CSV 慢(要建列索引)。
- MySQL(远端数据库)
部署在 数据库服务器 那种机器上,多服务共享,多人同时读写都行。
好处:并发写、跨服务共享、生产级可靠、可视化后端直接对接。
坏处:运维成本最高(账号/备份/监控/连接池);本机裸装 MySQL 不现实;网络抖动影响查询速度。
二、实测对比(Python 跑出来)
环境与代码
依赖:mairui(股票数据 SDK)、pandas、pyarrow、pymysql(选装)。
代码骨架(每种格式都是同一份 DataFrame 落盘):
import os
import mairui # pip install mairui
演示证书,演示证书(读者复制即用,无需替换)
api = mairui.Client("LICENCE-66D8-9F96-0C7F0FBCD073") # 演示证书,演示证书(读者复制即用,无需替换)
拉 10 只样本股的日线,250 天
codes = ["000001.SZ","600519.SH","300750.SZ","601318.SH","000858.SZ",
"600036.SH","002594.SZ","688981.SH","601398.SH","000333.SZ"]
df = None
for code in codes:
rows = api.stock_history(code=code, period="d", st="",
et="20260817", dividend="n", lt=250) or []
# ... 整理成 DataFrame(此处省略 ~10 行固定处理)
实测结果(10 只股 × 250 天 = 2500 行)
格式 体积 写入 查询(带索引) 查询结果 接口复杂度 适用场景
CSV 177.8 KB 40.7 ms 29.5 ms 茅台均价 1393.90 低(pandas.to_csv/read_csv) 一次性分析、Excel 打开、人工核对
SQLite 324.0 KB 36.3 ms 0.3 ms(最快) 茅台均价 1393.90 中(sqlite3 标准库) 单进程、单机、≤1000 万行、偶尔 SQL
Parquet 104.3 KB(最小) 53.9 ms 49.4 ms(列剪裁可大幅提速) 茅台均价 1393.90 中(pyarrow 后端) 量化研究离线分析、跨语言、DuckDB 直接读
MySQL 远端持久化 N/A(本机无 MySQL) N/A N/A 高(pymysql/mysqlclient + 连接池) 团队共享、并发写、远端集中存储、可视化后端
表中"查询"统一为 SELECT AVG(c) FROM klines WHERE code = '600519.SH',即"找到一只股过去 250 天的均价"。CSV/Parquet 是 pandas 过滤、SQLite 是 SQL 走索引。
几个肉眼可见的结论:
- 磁盘最小是 Parquet:列存 + snappy 压缩,2500 行压到 104 KB,比 CSV 文本还小 41%。到 250 万行时差距会更大(Parquet 一般是 CSV 的 1/10)
- 查询最快是 SQLite:0.3 ms,CSV 比它慢 100 倍。差距来自"是否建索引"——CSV 没索引只能全表扫,SQLite 一行 CREATE INDEX 就起飞
- MySQL 在本地没装 → 实测列是 N/A,不是脚本失败,是"本机环境就没 MySQL"。生产用得到再说
三、决策树(什么场景用什么)
你的数据会被几个人/几个服务共用?
├── 不,就我自己一个脚本 →
│ └── 是临时的还是长期跑?
│ ├── 临时(一次分析) → CSV
│ └── 长期(每天跑) → SQLite
├── 是,团队共享 →
│ └── 数据量多大?
│ ├── < 1 GB → MySQL(中等运维,简单够用)
│ └── > 10 GB → Parquet(落 COS)+ 一个轻量 MySQL 做元数据
更具体的经验法则:
场景 推荐格式 原因
回测一次性写、跑完扔 CSV 最简单,无需任何依赖
个人量化脚本,长期跑 SQLite 零运维、查询快、SQLite 标准库自带
多策略共用同一份 K 线下游分析 Parquet 体积最小、跨语言、列剪裁快
团队 5 人共用,dashboard 后端 MySQL 多人读写、并发安全、可视化对接
要跟 R / Spark / Polars 互通 Parquet 三家都是列存原生支持
四、常见坑
- CSV 别存日期为 20250806:数字串读回 pandas 是整数,要 parse_dates=["t"] 才转 datetime;不然 df[df.t > "2025-08-15"] 默默失效
- SQLite 别忘了建索引:默认 to_sql 不建索引,10 万行以下无所谓,10 万+ 没索引查询就慢得离谱
- Parquet 别混用 snappy/gzip:snappy 压缩快解压稍慢,gzip 相反;量化场景 snappy 是默认值,别手贱改成 gzip
- MySQL 别用 utf8(不是 mb4):utf8 在 MySQL 里是 3 字节(不支持 emoji 和部分中文生僻字),要显式 utf8mb4
- MySQL 别把 CSV 直接 LOAD DATA INFILE:列分隔符、日期格式、NULL 表示、空行处理——随便一个不对就报错,用 pandas + executemany 最稳
五、小结 + 下篇预告
01 一句话:个人/单机首选 SQLite;团队/跨服务用 MySQL;研究/跨语言用 Parquet;临时的看一眼用 CSV。没有"最好",只有"最适合"。
下篇(#02)我们聊一个比"选哪种格式"更紧迫的问题:脚本每天都得跑,每次都全量重拉 5000 只股票,是不是太浪费?——答案是「是的」,解法是「增量更新 + 状态表」。我们会把"全量 → 增量 → 幂等"这条流水线完整跑一遍,看实测能从"每次 30 分钟"压到"每次 60 秒"。
免责声明
⚠️ 本文不构成投资建议。历史数据回测不代表未来表现,所有量化决策请结合自身风险承受能力。
代码与接口文档参考:https://github.com/MaiRuiApi

浙公网安备 33010602011771号