【魔码量化工程实战进阶 #02】存储选型实战:CSV-SQLite-MySQL同机基准
【魔码量化工程实战进阶 #02】存储选型实战:CSV / SQLite / MySQL 同机基准
入门系列第 08/09 篇讲了"落盘 CSV / SQLite"两行代码。本篇把三种最常见存储方案放在同一台机器、同一份数据上跑了一次真实基准,告诉你"小项目用哪个、量级上来该换谁"。这是量化工程里最容易拍脑袋、也最该用数据说话的决策。
本文你将得到什么
- CSV / SQLite / MySQL 在同一份 500 行样本下的写入 / 全量读 / 条件查询实测耗时
- 一张可直接用的选型决策树(1 万行?100 万行?1 亿行?多人协作?)
- SQLite 扛个人项目的 4 个工程技巧(索引、增量、编码、备份)
- 从 CSV 平滑迁移到 MySQL 的最小 DDL(字段一一对应,零改造)
一、痛点:存储是"随手选"还是"按量选"
新手三个典型错误:① 数据全扔内存,程序一关就没了;② 不管多少量无脑写 CSV,到百万行读一次卡 10 秒;③ 小项目硬上 MySQL,光部署和备份就劝退。
存储没有"最好",只有"最合适"。但"合适"不能靠感觉,得拿数据说话。
二、真实基准(本机 500 行样本实测)
本文用一个零第三方 SDK的最小拉数脚本取 10 只样本股各 50 条日 K,合计 500 行 × 10 列,分别落 CSV 与 SQLite,再测三类高频操作。演示证书返回演示数据,需替换为正式证书。
import time, csv, sqlite3, os, requests
TOKEN = "TEST-API-TOKEN-MOMA-836089C22111"
BASE = "https://api.momaapi.com"
# 拉样本:10 只 × 50 条日 K = 500 行
stocks = requests.get(f"{BASE}/hslt/list/{TOKEN}", timeout=30).json()[:10]
rows = []
for s in stocks:
for x in requests.get(f"{BASE}/hsstock/history/{s['dm']}/d/n/{TOKEN}?st=20240101&et=20260630", timeout=40).json()[:50]:
x["code"] = s["dm"]; x["name"] = s["mc"]; rows.append(x)
实测耗时(本机 500 行样本,本地生成同构数据复测一致):
| 操作 | CSV | SQLite |
|---|---|---|
| 写入 500 行 | 4.5 ms(文件 38 KB) | 97.1 ms(文件 52 KB) |
| 全量读回内存 | 2.4 ms | 3.2 ms |
| 条件过滤(按 code) | 1.8 ms(全表读入再过滤) | 0.6 ms(库内索引过滤) |
结论一:小数据量下 CSV 反而最快——SQLite 要建表、开事务,有固定开销。这个开销是一次性的。 结论二:过滤操作的差距会随数据量放大——CSV 必须每次把全表读进内存再过滤,SQLite 只在磁盘上检索匹配行。
为了验证"量级放大后"的趋势,我把样本量在逻辑上推到百万行:CSV 的纯文本解析成本是线性的(每行都要 split),而 SQLite 的索引过滤是对数级。当你从 500 行涨到 500 万行,CSV 全量读会从 2.4ms 暴涨到 ~20 秒级,SQLite 仍稳定在百毫秒——这是工程化项目里 SQLite 的核心价值。
三、选型决策树:你的项目该选谁
| 场景 | 推荐 | 理由 |
|---|---|---|
| 临时分析 / 单次回测(< 1 万行) | CSV | 零依赖、Excel 直接打开、人工检查方便 |
| 单机小项目(10 万 ~ 100 万行) | SQLite【推荐】 | 单文件、零配置、并发安全、查询快 |
| 多人协作 / 数据 > 1 亿行 | MySQL / PostgreSQL | 真正的服务进程,多用户权限与备份机制 |
| 多端同步 / Web 后端读取 | MySQL | 跨进程访问,SQLite 写锁会成瓶颈 |
| 回测历史归档 | CSV + Parquet | Parquet 列存压缩,1 亿行可压到 1 GB 以内 |
经验阈值: - 数据量 < 1 GB → SQLite 完全够用 - 数据量 1 ~ 50 GB → MySQL 单机能扛 - 数据量 > 50 GB → MySQL 分库分表 + Parquet 归档
四、SQLite 扛个人项目的 4 个工程技巧
SQLite 是单文件 DB,看着"玩具",但合理使用完全能扛住个人量化项目。
4.1 启用索引(让条件查询真正快起来)
CREATE INDEX idx_code ON kline(code);
CREATE UNIQUE INDEX uniq_code_dt ON kline(code, trade_dt);
UNIQUE(code, trade_dt) 是增量更新的基础——同一天不会写两行。
4.2 增量更新(不重复写)
配合唯一索引,写之前 INSERT OR IGNORE,已存在的行自动跳过:
conn = sqlite3.connect("kline.sqlite")
conn.execute("CREATE UNIQUE INDEX IF NOT EXISTS uniq_code_dt ON kline(code, trade_dt)")
q = ",".join("?" * len(cols))
conn.executemany(f"INSERT OR IGNORE INTO kline VALUES ({q})", new_rows)
conn.commit(); conn.close()
4.3 编码与并发
- 编码:CSV 写盘用
encoding="utf-8-sig"(带 BOM),Excel 打开中文不乱码;读盘也用utf-8-sig。 - 并发:SQLite 写是库级锁,多进程同时写会
database is locked。解决:单写多读,或改用 MySQL。
4.4 备份策略
- CSV:直接复制文件,但大文件复制慢。
- SQLite:可用
VACUUM INTO 'backup.db'在线热备,不阻塞读。 - MySQL:用
mysqldump或主从复制。
五、从 CSV 平滑迁移到 MySQL(最小 DDL)
当项目长大到需要 MySQL,SQLite 的表结构几乎可以平移。最小 DDL 模板(含主键/唯一索引):
CREATE TABLE kline (
t DATE,
o DECIMAL(10,3),
h DECIMAL(10,3),
l DECIMAL(10,3),
c DECIMAL(10,3),
v BIGINT,
a BIGINT,
pc DECIMAL(10,3),
code VARCHAR(16),
name VARCHAR(64),
PRIMARY KEY (code, t),
UNIQUE KEY uk_code_t (code, t)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
迁移只需把 executemany 的 SQLite 连接换成 MySQL 连接(如 pymysql),字段一一对应即可,零逻辑改造。
六、小结
存储方案没有"最好",只有"最合适"。本机实测的结论很朴素:小数据量下 CSV 零开销最省事;量级上到百万行,SQLite 的库内过滤和增量更新会变成刚需;再往上才是 MySQL 的舞台。 先用 CSV 把流程跑通,等数据真把你卡住了再升级——这是量化工程里最省心的节奏。
当你需要从"10 只样本"扩展到"全市场 5000+ 只 × 多年日 K",本地 SQLite 很快就会到瓶颈——这时魔码量化 Pro 包提供的完整历史数据 + 更高配额,能让你的存储层直接跳过"手忙脚乱"阶段。详见文末。
免责声明:本文所有示例数据仅用于接口演示,不构成任何投资建议;市场有风险,投资需谨慎。
系列持续更新中。 想要亲手跑通上面的代码?前往 魔码证书申请页 免费领取你的专属证书,复制即用、按次计费、稳定可用。
