第九章 数据库选型与设计:时序数据库、关系型数据库与缓存策略

做量化交易系统,数据库选型是个绕不开的坎。我见过不少团队,一开始随便选个MySQL存行情数据,结果几个月后查询慢得像蜗牛爬。说白了,不同的数据有不同的脾气,你得顺着它来。

今天我们就聊聊衍生品做市商系统里,怎么搭配使用三种数据库:时序数据库、关系型数据库和缓存。嗯,这里要注意,不是选一个就完事了,而是让它们各司其职。

9.1 数据分类与存储需求

先理清我们到底要存什么数据。我习惯把数据分成三类:

  • 时序数据:行情快照、逐笔成交、订单簿快照。特点是写入频繁、查询按时间范围、几乎不修改。
  • 关系数据:账户信息、订单记录、成交明细、风控规则。特点是结构化、需要事务支持、经常关联查询。
  • 缓存数据:实时持仓、最新价格、限价单状态。特点是读写极快、允许短暂不一致、数据量小。

你想想看,如果把行情数据硬塞进关系型数据库,每次插入都要维护索引,写入性能直接崩掉。反过来,用时序数据库存订单记录,那查询关联数据时又得写一堆奇怪的SQL。

核心原则:让合适的数据库处理合适的数据。时序数据进时序库,关系数据进关系库,热数据进缓存。

9.2 时序数据库:InfluxDB vs ClickHouse

时序数据库这块,我主要用过InfluxDB和ClickHouse。两者都能处理海量时序数据,但设计理念完全不同。

9.2.1 InfluxDB:专为时序而生

InfluxDB的设计哲学就是「时序优先」。它的数据模型很简单:measurement(表)、tag(标签)、field(字段)、time(时间戳)。

我在项目中遇到过一个问题:刚开始用InfluxDB存逐笔成交数据,每秒写入几万条,查询最近1分钟的成交分布,响应时间都在10ms以内。但后来数据量到了TB级别,聚合查询开始变慢。

InfluxDB的强项是写入和简单查询。它的压缩率很高,磁盘占用比MySQL少5-10倍。但它的SQL-like查询语言(Flux)学习曲线有点陡,而且不支持JOIN。

-- InfluxDB写入示例(使用Flux)
from(bucket: "tick_data")
  |> range(start: -1h)
  |> filter(fn: (r) => r._measurement == "trade" and r.symbol == "BTC-USDT")
  |> aggregateWindow(every: 1m, fn: mean)
  |> yield(name: "mean_price")

9.2.2 ClickHouse:列式存储的猛兽

ClickHouse是列式存储数据库,严格来说不是纯粹的时序数据库,但做时序分析非常强。它的核心优势是:

  • 极致的查询性能:列式存储+向量化执行,聚合查询比InfluxDB快一个数量级
  • 标准SQL支持:团队上手成本低,可以JOIN、子查询
  • 高压缩比:我见过行情数据压缩到原始大小的1/10

但ClickHouse也有短板:写入是批量模式,不适合高频逐条写入。而且它的更新和删除操作很重,不适合频繁修改的数据。

-- ClickHouse创建时序表
CREATE TABLE trades (
    symbol String,
    price Float64,
    volume Float64,
    trade_time DateTime,
    side String
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(trade_time)
ORDER BY (symbol, trade_time);

我的建议:如果团队SQL功底好,优先选ClickHouse。如果追求极简运维和快速上手,InfluxDB更合适。我个人在主力系统里用ClickHouse,辅助监控用InfluxDB。

9.3 关系型数据库:PostgreSQL

关系型数据库这块,我几乎只用PostgreSQL。为什么?因为它够稳,功能够全,而且开源。

做市商系统里,订单和成交数据需要严格的事务保证。比如一个订单被部分成交,你需要原子性地更新订单状态和插入成交记录。PostgreSQL的ACID特性在这里是刚需。

我记得有一次排查一个bug,发现订单状态和成交记录对不上。后来用PostgreSQL的SERIALIZABLE隔离级别,配合SELECT ... FOR UPDATE,才彻底解决了并发问题。

-- PostgreSQL订单表设计
CREATE TABLE orders (
    order_id BIGSERIAL PRIMARY KEY,
    symbol VARCHAR(20) NOT NULL,
    side SMALLINT NOT NULL,  -- 1: buy, -1: sell
    price NUMERIC(20, 8) NOT NULL,
    volume NUMERIC(20, 8) NOT NULL,
    filled_volume NUMERIC(20, 8) DEFAULT 0,
    status SMALLINT DEFAULT 0,  -- 0: pending, 1: filled, 2: cancelled
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- 创建索引加速查询
CREATE INDEX idx_orders_symbol_status ON orders(symbol, status);
CREATE INDEX idx_orders_created_at ON orders(created_at DESC);

PostgreSQL还有一个杀手锏:分区表。订单数据量大了之后,按时间分区可以显著提升查询性能。我习惯按月分区,历史数据自动归档。

注意:不要用PostgreSQL存高频行情数据。我见过有人把tick数据塞进PG,结果写入TPS上不去,查询也慢。时序数据就该交给时序库。

9.4 Redis缓存策略

缓存层我用Redis,这个没什么好争议的。关键是缓存什么、怎么缓存、过期策略怎么定。

9.4.1 缓存什么

我总结了三类必须缓存的数据:

  • 实时行情:最新买卖价、最新成交价。延迟要求毫秒级。
  • 账户持仓:当前持仓数量、可用余额。每次下单前都要查。
  • 限价单状态:未成交订单的ID和价格。用于快速撤单和风控。

9.4.2 缓存策略

Redis的缓存策略,说白了就是「快」和「准」的平衡。我常用的几种模式:

# 1. 最新行情:使用String类型,设置过期时间
SET market:BTC-USDT:last_price "45000.50" EX 1  # 1秒过期

# 2. 订单簿快照:使用Hash类型
HSET orderbook:BTC-USDT bids "45000.00" "1.5" "44999.50" "2.0"
HSET orderbook:BTC-USDT asks "45001.00" "0.8" "45002.00" "1.2"

# 3. 账户持仓:使用Hash,持久化但设置过期
HSET account:user123:positions BTC-USDT "{\"total\": 10, \"available\": 8}"
EXPIRE account:user123:positions 3600  # 1小时过期,自动刷新

避坑指南:我曾经犯过一个错误——把Redis当数据库用,所有数据都不设过期时间。结果内存爆了,系统直接挂掉。记住,Redis是缓存,不是持久化存储。重要数据一定要落盘到PostgreSQL或ClickHouse。

9.4.3 缓存一致性

缓存和数据库的数据不一致,是交易系统的噩梦。我常用的策略是:

  • 先更新数据库,再删除缓存:保证最终一致性
  • 设置合理的过期时间:即使缓存没更新,过期后也会重新加载
  • 使用Redis的Pub/Sub:数据变更时广播通知,所有实例同步更新

9.5 整体架构与数据流

下面这张图是我在项目中实际使用的数据库架构。你可以看到数据是怎么流转的:

衍生品做市商系统数据库架构 行情数据源 数据接入层(Kafka/RabbitMQ) 时序数据库 ClickHouse / InfluxDB 关系型数据库 PostgreSQL 缓存 Redis 交易引擎 / 风控系统 / 监控面板 行情数据 → 数据接入层 → 分别写入时序库、关系库、缓存 交易引擎优先读取缓存,缓存未命中则回查数据库

这个架构的核心思路是:数据从行情源进来,经过消息队列削峰填谷,然后并行写入三个存储层。交易引擎读数据时,优先走Redis缓存,缓存没有再去查PostgreSQL或ClickHouse。

9.6 选型总结

最后,我整理了一个对比表格,方便你快速决策:

特性 InfluxDB ClickHouse PostgreSQL Redis
数据模型 时序专用 列式存储 关系型 键值对
写入性能 极高(单机百万/秒) 高(批量写入) 中等 极高(内存操作)
查询性能 时序查询快 聚合查询极快 关联查询强 单键查询极快
事务支持 不支持 有限支持 完整ACID 有限支持
适用场景 行情快照、监控 历史分析、报表 订单、账户、风控 实时数据、会话
运维复杂度

一句话总结:行情数据进ClickHouse,订单账户进PostgreSQL,热数据进Redis。三者配合,才能支撑起一个高性能的做市商系统。

嗯,数据库选型这块就聊这么多。记住,没有银弹,只有最适合你场景的组合。我在项目中踩过的坑,希望你能避开。


交易系统化学习资料 微信Strategy888888