← 返回博客列表

【魔码量化工程实战进阶 #02】存储选型实战:CSV-SQLite-MySQL同机基准

2026年08月31日 18:00 · 魔码数服 · 魔码量化工程实战进阶

【魔码量化工程实战进阶 #02】存储选型实战:CSV / SQLite / MySQL 同机基准

入门系列第 08/09 篇讲了"落盘 CSV / SQLite"两行代码。本篇把三种最常见存储方案放在同一台机器、同一份数据上跑了一次真实基准,告诉你"小项目用哪个、量级上来该换谁"。这是量化工程里最容易拍脑袋、也最该用数据说话的决策。

本文你将得到什么

  1. CSV / SQLite / MySQL 在同一份 500 行样本下的写入 / 全量读 / 条件查询实测耗时
  2. 一张可直接用的选型决策树(1 万行?100 万行?1 亿行?多人协作?)
  3. SQLite 扛个人项目的 4 个工程技巧(索引、增量、编码、备份)
  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 包提供的完整历史数据 + 更高配额,能让你的存储层直接跳过"手忙脚乱"阶段。详见文末。


免责声明:本文所有示例数据仅用于接口演示,不构成任何投资建议;市场有风险,投资需谨慎。

系列持续更新中。 想要亲手跑通上面的代码?前往 魔码证书申请页 免费领取你的专属证书,复制即用、按次计费、稳定可用。

想亲自试一下?免费获取证书
客服微信
客服微信二维码