4. 数据存储方案:时序数据库选型(InfluxDB、ClickHouse)、关系型数据库(MySQL、PostgreSQL)、数据分片与归档策略

聊到量化交易系统的数据存储,我估计很多朋友第一反应就是「MySQL一把梭」。嗯,我刚开始做量化系统时也是这么干的。结果呢?行情数据一上来,单表几千万条记录,查询直接卡死。后来我才明白——不同的数据,就该用不同的数据库来管

今天咱们就把这块掰开揉碎了讲。核心就三件事:时序数据用什么存、关系数据用什么存、数据太多怎么切怎么扔

4.1 时序数据库选型:InfluxDB vs ClickHouse

先说说时序数据。说白了,就是带时间戳的、不断追加写入的数据。比如每笔Tick、每分钟K线、每秒钟的盘口快照。这类数据有几个特点:写入量大、几乎不更新、按时间范围查询多

我个人习惯把时序数据库分成两类:专用型通用型。InfluxDB是专用型的代表,ClickHouse则是通用型里做时序做得最好的。

4.1.1 InfluxDB:专为时序而生

InfluxDB的设计哲学就是「为时序数据优化一切」。它的数据模型很简单:measurement + tags + fields + timestamp。你想想看,这不就是K线数据的天然映射吗?

核心优势:写入吞吐极高,单机每秒能处理几十万条数据点。而且它的数据压缩率很惊人,我实测过,原始文本数据压缩比能达到10:1以上。

我在项目中遇到过一个问题:InfluxDB的查询语法是类SQL的Flux语言,跟标准SQL差别挺大。团队里新来的同学经常写错。后来我干脆封装了一层查询接口,把常用的查询模式固定下来。

// InfluxDB Flux查询示例:获取最近1小时的1分钟K线
from(bucket: "kline_1m")
  |> range(start: -1h)
  |> filter(fn: (r) => r._measurement == "kline" and r.symbol == "rb2401")
  |> aggregateWindow(every: 1m, fn: last)

注意:InfluxDB的集群版是商业产品,开源版只有单机。如果你的数据量超过单机承载(比如每天新增10亿条以上),就得考虑其他方案了。

4.1.2 ClickHouse:列式存储的时序利器

ClickHouse本来是个通用分析型数据库,但它在时序场景的表现实在太亮眼了。为什么?因为它用了列式存储。你想想看,查询时序数据时,我们通常只关心某几个字段(比如收盘价、成交量),列式存储能只读取需要的列,IO开销小得多。

我记得有一次做性能对比:同样查询1000万条K线数据,InfluxDB用了2.3秒,ClickHouse只用了0.8秒。而且ClickHouse支持标准SQL,团队上手成本低很多。

-- ClickHouse查询示例:计算过去30天的日均成交量
SELECT 
    toDate(timestamp) as trade_date,
    avg(volume) as avg_volume
FROM kline_1d
WHERE symbol = 'rb2401'
  AND timestamp >= now() - INTERVAL 30 DAY
GROUP BY trade_date
ORDER BY trade_date

我的建议:如果团队规模小、数据量在TB级以内,用InfluxDB就够了。如果数据量大、查询复杂、团队熟悉SQL,优先选ClickHouse。我现在的主力系统就是ClickHouse + MySQL的组合。

4.2 关系型数据库:MySQL vs PostgreSQL

时序数据搞定了,那交易订单、账户信息、策略配置这些数据呢?这些必须用关系型数据库。为什么?因为事务一致性。你想想看,一笔交易从下单到成交,涉及订单表、持仓表、资金表,任何一个环节出错都不行。

4.2.1 MySQL:轻量级首选

MySQL的优势在于生态成熟、运维简单。对于中小型量化团队,MySQL完全够用。我早期做的一个CTA策略系统,就用MySQL存了3年的交易记录,单表2000万条,配合合理的索引,查询响应都在100ms以内。

不过要注意一点:MySQL的JSON支持比较弱。量化系统里经常需要存一些非结构化的策略参数,比如神经网络权重、自定义指标配置。MySQL处理起来比较吃力。

-- MySQL订单表设计示例
CREATE TABLE trade_orders (
    order_id BIGINT AUTO_INCREMENT PRIMARY KEY,
    strategy_id INT NOT NULL,
    symbol VARCHAR(20) NOT NULL,
    direction ENUM('buy', 'sell') NOT NULL,
    price DECIMAL(10, 2) NOT NULL,
    volume INT NOT NULL,
    status ENUM('pending', 'filled', 'cancelled') NOT NULL,
    created_at DATETIME NOT NULL,
    INDEX idx_strategy_time (strategy_id, created_at)
) ENGINE=InnoDB;

4.2.2 PostgreSQL:复杂查询的王者

PostgreSQL呢?说实话,我是在做高频交易系统时才真正体会到它的好。它的JSONB数据类型可以完美存储策略参数,而且支持索引。还有它的窗口函数,做资金曲线计算、回撤分析时特别方便。

个人经验:如果你的系统需要做复杂的风控计算(比如实时计算所有策略的总敞口、最大回撤),PostgreSQL的CTE(公用表表达式)和窗口函数能让你少写一半代码。

-- PostgreSQL窗口函数示例:计算每个策略的累计收益
SELECT 
    strategy_id,
    trade_date,
    daily_pnl,
    SUM(daily_pnl) OVER (PARTITION BY strategy_id ORDER BY trade_date) as cumulative_pnl
FROM strategy_daily_pnl
WHERE trade_date >= '2024-01-01'
ORDER BY strategy_id, trade_date;

选型建议:团队小、业务简单 -> MySQL。团队有DBA、业务复杂、需要高级功能 -> PostgreSQL。我现在两个都用:MySQL存订单流水,PostgreSQL存策略分析和风控数据。

4.3 数据分片与归档策略

数据量大了怎么办?这是每个量化系统都会遇到的问题。我见过最夸张的案例:某团队把5年的Tick数据全塞在一个表里,查询一次要等10分钟。嗯,这显然不行。

4.3.1 数据分片:水平拆分

分片的思路很简单:把大表拆成小表。最常用的方式是按时间分片。比如K线数据,按月分表:kline_1m_202401、kline_1m_202402……查询时只查对应月份的表。

-- 按月分表示例(ClickHouse)
CREATE TABLE kline_1m_202401 AS kline_1m_202312
ENGINE = MergeTree()
PARTITION BY toYYYYMM(timestamp)
ORDER BY (symbol, timestamp);

除了按时间分片,还可以按品种分片。比如把螺纹钢的数据放在一个库,把铁矿石的数据放在另一个库。这样查询特定品种时,IO压力分散了,速度自然快。

避坑指南:我曾经犯过一个错误——分片粒度太细。比如按天分表,结果每天一张表,一年下来365张表。查询跨天数据时,要UNION ALL一堆表,代码又臭又慢。后来我改成按月分片,清爽多了。

4.3.2 数据归档:冷热分离

数据分片解决了「查得快」的问题,但「存得下」还得靠归档。说白了,就是把不常用的历史数据挪到便宜的地方去

我的归档策略一般是这样的:

  • 热数据(最近3个月):放在SSD上,查询响应要求毫秒级
  • 温数据(3个月到2年):放在普通HDD上,查询响应秒级可接受
  • 冷数据(2年以上):压缩后存到对象存储(比如MinIO),查询需要先解压加载

归档的触发条件呢?我习惯用定时任务 + 数据量阈值双保险。比如每天凌晨检查一次,如果某张表的数据超过500GB,就自动触发归档流程。

# 归档脚本伪代码
def archive_old_data():
    # 检查哪些表需要归档
    tables = get_tables_older_than(months=6)
    for table in tables:
        # 导出为Parquet格式(压缩率高)
        export_to_parquet(table, f"/archive/{table}.parquet")
        # 从在线库中删除已归档数据
        drop_table_partition(table, before_date)
        # 记录归档日志
        log_archive(table, status="success")

一个小技巧:归档后的数据不要直接删掉,保留一份元数据索引。比如记录「2023年螺纹钢Tick数据在archive_2023.parquet的第100-200MB位置」。这样万一需要回溯查询,还能快速定位。

4.4 整体架构图

说了这么多,咱们用一张图把整个数据存储方案串起来。这张图展示了我目前在用的一个生产级架构:

量化交易系统数据存储架构 数据源层 行情数据(Tick/K线/盘口) | 交易数据(订单/成交) | 基本面数据 数据接入层 Kafka消息队列 | 数据清洗 | 格式转换 存储层 时序数据库 InfluxDB / ClickHouse 存储Tick、K线、盘口 关系型数据库 MySQL / PostgreSQL 存储订单、账户、策略 对象存储 MinIO / S3 存储归档冷数据 数据管理策略 分片策略(按时间/品种) | 归档策略(冷热分离) | 备份与恢复

这张图的核心思想就是分层解耦。数据从源进来,经过清洗转换,根据类型分流到不同的存储引擎。再通过分片和归档策略,保证系统既能扛住高并发写入,又能高效查询历史数据。

最后说一句:没有完美的存储方案,只有适合你的方案。我见过用MySQL硬扛Tick数据的,也见过用ClickHouse存订单的。关键是要理解每种数据库的设计哲学和适用场景。选对了,事半功倍;选错了,后面全是坑。


公众号:蓝海资料掘金营,微信deep3321