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是常事。

核心结论:MySQL适合Tick数据量小(日均百万级以下)、查询简单的场景。别指望它处理海量数据。

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);
我的经验:PostgreSQL配合BRIN索引,在日均千万级Tick数据下,查询性能可以维持在毫秒级。但写入吞吐量受限于单机磁盘IO,建议用SSD。

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"
避坑指南:我曾经在生产环境遇到过InfluxDB的TSM文件合并导致查询延迟飙升。原因是默认的合并策略太激进,IO被打满。建议把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');
我的建议:TDengine适合场景单一、时间序列数量可控(几百个以内)的场景。如果你有几千个标的,管理成本会很高。

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),压缩比高,解压速度快
我的经验:ClickHouse在单机环境下,处理百亿级Tick数据毫无压力。我曾在48核、256GB内存的机器上,用ClickHouse存储某交易所全量Tick数据,日均查询延迟在50ms以内。

3.4 选型对比总结

说了这么多,最后给个直观的对比。我根据自己的实战经验,整理了一张表:

维度 MySQL PostgreSQL InfluxDB TDengine ClickHouse
写入吞吐 极高
查询延迟 极低
压缩比 极高
运维复杂度
适用数据量 百万级 千万级 亿级 十亿级 百亿级

选型没有银弹。我个人习惯是:小项目用PostgreSQL,中等规模用InfluxDB,大规模生产环境直接上ClickHouse。TDengine适合场景单一、团队有国产化需求的场景。

嗯,这一章就到这里。记住一点:选型不是终点,调优才是。下一章咱们会深入ClickHouse的MergeTree引擎,看看它到底是怎么做到这么快的。

Tick数据存储引擎选型全景图 Tick数据存储引擎 关系型数据库 时序数据库 列式存储 MySQL PostgreSQL InfluxDB TDengine ClickHouse B+树索引 行式存储 BRIN索引 分区表 TSM引擎 高写入 超级表 单表极快 MergeTree 列式+向量化 推荐场景 小规模 → PostgreSQL | 中等规模 → InfluxDB | 大规模 → ClickHouse 选型建议:根据数据量、查询模式、运维能力综合评估 百万级 千万~亿级 十亿~百亿级

专注资料整理