3. 主流存储引擎概览:关系型数据库、时序数据库、列式存储
做Tick数据存储,选型是第一步。说实话,市面上能用的引擎不少,但真正适合高频金融数据的,也就那么几类。我这些年踩过的坑,基本都集中在选型阶段——选错了,后面再怎么调优都白搭。
今天咱们把三大类引擎过一遍:关系型数据库、时序数据库、列式存储。每类我都挑两个代表,讲讲它们的核心设计、适用场景,以及我在实战中遇到的坑。
3.1 关系型数据库:MySQL 与 PostgreSQL
很多人一听Tick数据,第一反应就是上MySQL。嗯,这想法我能理解,毕竟MySQL太普及了。但说实话,用它存Tick数据,就像用轿车拉货——不是不行,但你会很痛苦。
3.1.1 MySQL:简单场景的备选
MySQL的InnoDB引擎,默认按主键聚簇索引组织数据。对于Tick数据,如果你按时间戳做主键,写入会变成顺序追加,性能还行。但问题出在查询上——你想想看,金融数据经常要查某个时间窗口内的所有记录,这就要走范围扫描。InnoDB的B+树在范围查询上效率不错,但一旦数据量上亿,IO开销就上来了。
我在项目中遇到过,用MySQL存某期货品种的Tick数据,一天大概500万条。单表查询还能接受,但跨月查询直接卡死。后来加了分区表,按月分区,勉强能用。但说实话,MySQL的压缩能力太弱,一张表膨胀到几十GB是常事。
3.1.2 PostgreSQL:更优的关系型选择
PostgreSQL比MySQL更适合Tick数据,原因有三:
- 更好的并发控制:MVCC实现更成熟,读写不互斥
- 原生分区表:声明式分区,管理方便
- 扩展能力:支持自定义类型、索引插件
我个人习惯用PostgreSQL的BRIN索引来处理时间序列数据。BRIN索引不像B+树那样记录每条数据的位置,而是按数据块记录统计信息。对于按时间顺序写入的Tick数据,BRIN索引的存储开销极小,查询效率却很高。
-- 创建分区表示例
CREATE TABLE tick_data (
symbol TEXT NOT NULL,
ts TIMESTAMPTZ NOT NULL,
price NUMERIC(10,2),
volume INTEGER
) PARTITION BY RANGE (ts);
-- 按月创建分区
CREATE TABLE tick_data_202401 PARTITION OF tick_data
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
-- 创建BRIN索引
CREATE INDEX idx_tick_ts_brin ON tick_data USING BRIN (ts)
WITH (pages_per_range = 32);
3.2 时序数据库:InfluxDB 与 TDengine
时序数据库是专门为时间序列数据设计的。说白了,它们就是为Tick数据量身定做的。但不同产品之间差异很大,选错了照样踩坑。
3.2.1 InfluxDB:灵活但资源消耗大
InfluxDB的TSM存储引擎,设计思路很巧妙。它把数据先写入内存中的写缓存(WAL),然后定期合并成只读文件。这种设计让写入性能极高——我测试过,单机每秒能写几十万点。
但InfluxDB有个大问题:内存消耗。它的倒排索引和TSM文件的合并过程,对内存需求很大。我曾经在64GB内存的机器上跑InfluxDB,数据量到10亿条时,内存直接吃满,OOM了两次。后来调了cache-max-memory-size参数,才稳住。
# InfluxDB配置调优示例
[data]
# 限制缓存大小,防止OOM
cache-max-memory-size = "16g"
# 调整TSM文件合并策略
compact-throughput = "48m"
compact-throughput-burst = "96m"
compact-throughput调低,给查询留点余量。
3.2.2 TDengine:国产黑马,但有限制
TDengine是涛思数据的产品,设计上很激进。它把每个表当成一个独立的时间序列,用列式存储加时间戳主键排序。这种设计让它在单表查询上极快——我测试过,10亿条数据,按时间范围查询,毫秒级返回。
但TDengine有个硬伤:它要求每个时间序列单独建表。对于股票Tick数据,如果你有几千只股票,就要建几千张表。管理起来很麻烦。而且TDengine的SQL支持不够完整,复杂查询(比如多表JOIN)性能一般。
-- TDengine创建超级表(模板)
CREATE STABLE tick_data (
ts TIMESTAMP,
price DOUBLE,
volume INT
) TAGS (symbol BINARY(10));
-- 为每只股票创建子表
CREATE TABLE tick_000001 USING tick_data TAGS ('000001');
CREATE TABLE tick_000002 USING tick_data TAGS ('000002');
3.3 列式存储:ClickHouse
ClickHouse是我个人最推荐的Tick数据存储引擎。它用列式存储加向量化执行,查询性能碾压前面所有选手。
3.3.1 列式存储的核心优势
Tick数据的特点是:字段多(时间、价格、成交量、买卖盘口等),但每次查询只关心其中几个字段。列式存储只读取需要的列,IO量大幅减少。举个例子,如果你只查价格和成交量,行式存储要读整行数据,列式存储只读两列,IO量可能差10倍。
ClickHouse的MergeTree引擎家族,专门为时间序列优化。它按时间分区,每个分区内按排序键组织数据。我习惯用ORDER BY (symbol, ts),这样同一只股票的数据物理上连续存储,查询效率极高。
-- ClickHouse建表示例
CREATE TABLE tick_data (
symbol String,
ts DateTime64(3),
price Float64,
volume UInt32,
bid_price Float64,
ask_price Float64
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(ts)
ORDER BY (symbol, ts)
SETTINGS index_granularity = 8192;
3.3.2 实战调优要点
ClickHouse虽然强,但调优不到位,性能也出不来。我总结几个关键点:
- 分区粒度:按天或按月分区。太细(比如按小时)会导致小文件太多,合并压力大
- 排序键:把最常用的查询条件放前面。我一般用
(symbol, ts) - 索引粒度:默认8192,对于高频Tick数据可以调大到16384,减少索引体积
- 压缩算法:用
CODEC(ZSTD),压缩比高,解压速度快
3.4 选型对比总结
说了这么多,最后给个直观的对比。我根据自己的实战经验,整理了一张表:
| 维度 | MySQL | PostgreSQL | InfluxDB | TDengine | ClickHouse |
|---|---|---|---|---|---|
| 写入吞吐 | 低 | 中 | 高 | 高 | 极高 |
| 查询延迟 | 高 | 中 | 低 | 低 | 极低 |
| 压缩比 | 低 | 中 | 高 | 高 | 极高 |
| 运维复杂度 | 低 | 中 | 高 | 中 | 中 |
| 适用数据量 | 百万级 | 千万级 | 亿级 | 十亿级 | 百亿级 |
选型没有银弹。我个人习惯是:小项目用PostgreSQL,中等规模用InfluxDB,大规模生产环境直接上ClickHouse。TDengine适合场景单一、团队有国产化需求的场景。
嗯,这一章就到这里。记住一点:选型不是终点,调优才是。下一章咱们会深入ClickHouse的MergeTree引擎,看看它到底是怎么做到这么快的。