股票数据本地化存储实战:JSON、数据库与列式存储的方案对比

股票数据本地化存储实战:JSON、数据库与列式存储的方案对比

如果你打算把股票数据存到本地,第一件事就是选型。我在这条路上踩过不少坑,从最初的纯JSON文件存储,到后来的SQLite数据库,再到现在用的列式数据库,每一步都有故事。这篇文章我把三种方案在股票数据场景下的优劣做一个详细对比,包括写入性能、查询性能、维护成本、适用场景等维度,并用实际数据做基准测试,帮你少走弯路。

先明确股票数据的特点

在讨论存储方案之前,得先理解股票数据的独特性。它和普通的业务数据不一样,有几个鲜明的特征:

一是数据量随时间线性增长。每只股票每个交易日产生一条日K线,A股5000多只股票,每年250个交易日,一年就是125万条记录。如果加上分钟级别数据,量会更大。

二是写入模式以追加为主。K线数据基本只会增加,很少修改。实时行情虽然是覆盖式更新,但历史数据是追加写入。

三是查询模式以聚合和扫描为主。分析时很少按单条记录查,更多的是按时间范围、按行业、按指标做聚合运算(求均值、求和、分组统计),这时候列式存储的优势就体现出来了。

四是时间序列特征明显。股票数据天然带有时间维度,按日期分表或按日期分区是常见的做法。

理解了这些特点,再来看三种存储方案的对比。

方案一:JSON文件存储

这是我最早用的方案。每只股票一个JSON文件,存放在按日期组织的目录里。比如data/daily/20260810/600519.json,文件内容是当天的行情数据。

json 复制代码
{
    "dm": "600519",
    "mc": "贵州茅台",
    "cjsj": "20260810",
    "open": 1680.00,
    "high": 1720.50,
    "low": 1675.00,
    "cjjg": 1715.80,
    "cjl": 3500000,
    "amount": 6005000000
}

JSON方案的优点是简单直观,不需要任何数据库依赖,读写用标准库的json模块就行。文件备份和迁移也很方便,直接拷贝目录。我的第一版原型用的就是这个方案,半天就搞定了。

但很快就遇到了问题。第一个问题是查询。比如我想查贵州茅台2024年全年的收盘价,需要遍历250个JSON文件,每个文件都要打开、解析、提取字段,跑一次要好几秒。如果要按行业统计所有白酒股的平均成交量,那简直是灾难------要打开几千个文件,做几千次JSON解析。

第二个问题是数据一致性。JSON文件没有事务支持,如果在写入过程中程序崩溃,可能产生半写的文件。我遇到过这种情况:某次批量更新跑到一半重启了,导致部分股票当天的数据缺失,排查起来很麻烦。

第三个问题是空间效率。JSON的文本格式非常冗余,比如字段名"cjjg"在每个文件里都要存一遍。我测了一下,同样的数据量,JSON占用的空间是二进制格式的3到5倍。

方案二:SQLite数据库

从JSON切换到SQLite是一个自然的选择。SQLite是单文件数据库,支持标准SQL,零部署,非常适合个人项目和小团队。

sql 复制代码
CREATE TABLE kline (
    dm TEXT NOT NULL,
    cjsj TEXT NOT NULL,
    cjjg REAL,
    cjl REAL,
    PRIMARY KEY (dm, cjsj)
);
CREATE INDEX idx_kline_cjsj ON kline(cjsj);

SQLite的优势非常明显。首先是查询能力,用SQL做聚合、排序、分组都很方便。比如查贵州茅台2024年全年收盘价,一行SQL就搞定:

sql 复制代码
SELECT cjsj, cjjg FROM kline
WHERE dm='600519' AND cjsj BETWEEN '20240101' AND '20241231'
ORDER BY cjsj;

其次是事务支持,批量写入时用BEGIN/COMMIT包裹,要么全部写入要么全部回滚,数据一致性有保障。

第三是空间效率,内部用二进制存储,比JSON小很多。我测了同样5000只股票一年日K线的数据:JSON约45MB,SQLite约12MB,压缩比接近4:1。

但SQLite也有局限。最主要的是并发能力,写入操作是串行的,多个进程同时写入会锁库。对于我们这种场景(每天定时批量写入)问题不大,但如果需要高频实时写入行情数据,就会遇到瓶颈。

另一个问题是分析性能。当数据量达到百万级后,复杂的聚合查询会变慢。比如要计算全市场所有股票过去一年的平均换手率,SQLite需要扫描全表,耗时可能在秒级。

方案三:列式存储(DuckDB)

为了解决分析性能的问题,我开始调研列式存储。列式数据库的核心思想是数据按列存储而不是按行存储,这样在做聚合查询时只读需要的列,IO效率大幅提升。我选择了DuckDB,它和SQLite一样是嵌入式数据库,零部署,但内部是列式架构。

python 复制代码
import duckdb

conn = duckdb.connect("stock.duckdb")
conn.execute("""
    CREATE TABLE kline (
        dm VARCHAR,
        cjsj VARCHAR,
        cjjg DOUBLE,
        cjl DOUBLE
    )
""")
conn.execute("CREATE INDEX idx_cjsj ON kline(cjsj)")

DuckDB的查询性能确实惊人。同样的全市场聚合查询,SQLite要3秒,DuckDB只需要0.2秒。而且DuckDB支持向量化计算,在做因子计算(比如对每只股票求过去20天的移动平均)时,速度比SQLite快一个数量级。

DuckDB的另一个优点是和Python生态的集成非常好,直接支持pandas DataFrame的读写,做数据分析时可以无缝衔接。

不过DuckDB也有不足。一是写入性能,由于是列式存储,单行写入效率较低,适合批量写入。二是生态相对年轻,文档和社区资源不如SQLite丰富。三是对于高频小更新的场景(比如实时行情逐笔写入),列式存储不是最优选择。

基准测试:三种方案的真实较量

说了这么多,不如直接上数据。我用全市场5000只股票、2023-2025共3年的日K线数据做了基准测试。数据总量约375万条记录(5000股 × 250天/年 × 3年)。

测试环境是Windows 11,Python 3.11,SQLite 3.43,DuckDB 1.1。JSON用的是标准json模块。

测试一:写入性能

测试方法:将375万条K线数据写入空库,记录总耗时。

JSON方案采用每个股票一个文件,并行写入(8线程),耗时约180秒。

SQLite采用批量INSERT,用BEGIN/COMMIT包裹,耗时约45秒。

DuckDB采用批量INSERT,耗时约30秒。

方案 写入耗时 单条平均
JSON(并行) 180秒 480μs
SQLite 45秒 120μs
DuckDB 30秒 80μs

测试二:单股时间范围查询

测试方法:查询贵州茅台3年的日K线(约750条记录)。

JSON:遍历750个文件,逐个解析提取字段,耗时约3.2秒。

SQLite:带索引的范围查询,耗时约15ms。

DuckDB:同样的查询,耗时约5ms。

方案 查询耗时
JSON 3200ms
SQLite 15ms
DuckDB 5ms

测试三:全市场聚合查询

测试方法:计算每个行业的平均收盘价、平均成交量。

JSON:需要遍历所有文件、解析、提取、分组、聚合,耗时超过60秒(实际上我没等完)。

SQLite:用SQL的GROUP BY,耗时约2.8秒。

DuckDB:同样的SQL,耗时约0.15秒。

sql 复制代码
SELECT hy, AVG(cjjg), AVG(cjl)
FROM kline
JOIN stock_info ON kline.dm = stock_info.dm
WHERE cjsj BETWEEN '20230101' AND '20251231'
GROUP BY hy;
方案 聚合查询耗时
JSON >60秒
SQLite 2.8秒
DuckDB 0.15秒

测试四:存储空间

同样375万条数据:

JSON文件:约168MB

SQLite:约42MB

DuckDB:约38MB

(如果用Parquet格式存储,压缩后约12MB)

选型建议

综合以上测试结果,给出我的选型建议:

如果你是纯新手,或者只是做一些简单的单股票分析,JSON方案可以先用起来,快速验证想法。但要注意控制数据量,建议按日期分目录,避免单个目录文件过多。

如果你是个人开发者或小团队,需要做全市场的数据分析,SQLite是最佳选择。它的性能在大部分场景下够用,而且零运维成本。我的建议是按年分库,比如stock_2023.db、stock_2024.db,避免单库过大。

如果你有更高的分析需求,比如需要频繁做跨周期、跨行业的聚合运算,或者需要做因子回测,DuckDB是更好的选择。它的列式架构在分析型查询中优势明显,而且和Python生态的集成非常顺畅。

我的实际做法是混合使用:历史K线用DuckDB存储,方便做复杂分析;实时行情用SQLite,方便快速查询;基础信息(股票列表、行业分类)用JSON文件,方便配置管理。这样各取所长,覆盖不同的使用场景。

最后说一点,存储方案不是一成不变的。我自己的路径是:JSON → SQLite → DuckDB,每一步都是因为需求推动的。不用一开始就追求最优解,先用起来,当现有方案遇到瓶颈时再升级。对于股票数据存储来说,够用比完美更重要。

接口说明

接口路径 用途 核心参数 核心返回字段
base/gplist 获取全市场股票列表 - dm, mc, hy, ssrq
time/real/{dm} 获取实时行情 dm=股票代码 f43(现价), f47(成交量), f58(时间), f169(方向)
time/history/trade/{dm}/{level} 获取历史K线 dm=代码, level=周期 klines(时间,开,收,高,低,量)
time/f10/fi/{dm} 获取财务指标 dm=股票代码 营收, 净利润, ROE等
time/real/trace/l2sign/{dm} 获取L2指标 dm=股票代码 ddx, ddy, ddz, ddf
time/real/trace/onebyone/{dm} 获取逐笔交易 dm=股票代码 cjsj, cjjg, cjl, jyzd
time/zijin/zlzjzs/{dm} 获取资金走势 dm=股票代码 zlJlr, zlJlb, shJlb
time/zijin/zjlrqs/{dm} 获取资金趋势 dm=股票代码 f5MinZlJe等
time/data/longhubang 获取龙虎榜数据 日期 营业部, 买入金额, 卖出金额
time/data/bshgt 获取北向资金数据 日期 沪股通, 深股通净流入

资料参考:ig50.com

相关推荐
Exclusive_Cat3 小时前
MySQL回表机制解析与优化策略
数据库·sql
circuitsosk3 小时前
向量数据库选型与性能压测:Milvus、Pinecone、Chroma在真实业务下的对比
数据库·python·pinecone·milvus·向量数据库·chroma
影寂ldy4 小时前
SQL 索引(Index)完整笔记
数据库·笔记·sql
切糕师学AI4 小时前
从压缩到查询:PostgreSQL中JSON数据的存储与处理实践
数据库·postgresql·json
渣渣盟4 小时前
当 Redis 写入成为性能瓶颈时,如何利用 异步批量 Sink 将吞吐量从 1w QPS 提升到 10w+?
数据库·redis·php
SelectDB4 小时前
Apache Doris 2026 Roadmap:AI 成为主流负载后数据基础设施的演进方向
数据库
SelectDB4 小时前
瓴岳科技洋钱罐 SelectDB Hive 透明加速:湖仓一体化探索分析平台升级实践
数据库
SelectDB4 小时前
飞轮科技 SelectDB Enterprise 4.0.5:企业级实时分析与 AI 数据底座的安全治理实践
数据库
SelectDB4 小时前
金城银行 Apache Doris 实时数据平台:从 T+1 到分钟级的金融级高可靠实践
数据库