数据库选型与配置:关系型 vs 时序数据库
做量化实盘,数据库选型是个绕不开的坎。我见过不少团队,一开始随便选个MySQL存行情,结果跑了一周,查询速度慢得像蜗牛。说白了,选错数据库,你的策略再牛也白搭。
今天咱们聊聊关系型数据库和时序数据库的区别,以及怎么搭配合适。我个人习惯把数据库选型分成两步:先看数据类型,再看查询模式。
关系型数据库:MySQL vs PostgreSQL
关系型数据库适合存什么?订单、持仓、用户信息、策略配置。这些数据的特点是:结构固定、需要事务支持、经常做增删改查。
MySQL 和 PostgreSQL 我都用过。MySQL上手快,社区资源多,但遇到复杂查询就有点吃力。PostgreSQL功能更强,支持窗口函数、CTE、JSON字段,做回测分析时特别顺手。
举个例子,查某个策略最近30天的每日盈亏:
-- PostgreSQL 写法,简洁高效
SELECT
date_trunc('day', trade_time) as trade_date,
SUM(pnl) as daily_pnl
FROM trades
WHERE strategy_id = 'strat_001'
AND trade_time >= NOW() - INTERVAL '30 days'
GROUP BY 1
ORDER BY 1;
MySQL写同样的逻辑,得用DATE_FORMAT函数,性能还差一截。我个人建议:新项目优先选PostgreSQL,除非你团队里全是MySQL老手。
核心结论:关系型数据库存「状态」数据,时序数据库存「过程」数据。别混着用。
时序数据库:InfluxDB vs ClickHouse
行情数据、Tick数据、K线数据,这些是时序数据库的强项。为什么不用MySQL?你想想看,每秒几千条Tick写入,MySQL的B+树索引扛不住,写入延迟会越来越高。
InfluxDB 专为时序场景设计,写入快,查询语法简单。我早期做高频策略时就用它存Tick数据。但有个坑:InfluxDB的存储压缩率一般,数据量大了以后磁盘占用很吓人。
ClickHouse 是后来崛起的列式存储数据库。它的压缩率惊人,同样的数据量,磁盘占用只有InfluxDB的1/3到1/5。查询速度也快,特别是做聚合分析时。
我曾经在项目里用ClickHouse存全市场Level-2行情,每天数据量约50GB,查询1分钟K线只需要几十毫秒。InfluxDB做同样的查询,得等好几秒。
| 特性 | InfluxDB | ClickHouse |
|---|---|---|
| 写入速度 | 极快 | 快 |
| 查询速度(聚合) | 中等 | 极快 |
| 存储压缩率 | 一般 | 高 |
| 学习成本 | 低 | 中 |
| 适用场景 | 中小规模实时监控 | 大规模历史分析 |
我的建议:实时监控用InfluxDB,历史回测和数据分析用ClickHouse。两者可以共存,各司其职。
数据库连接池配置
连接池配置看似简单,但坑不少。我见过有人把连接池设成100,结果数据库直接挂了。为什么?因为每个连接都占用内存和文件句柄,太多连接反而拖垮性能。
以Python的 SQLAlchemy 为例,我常用的配置:
from sqlalchemy import create_engine
engine = create_engine(
'postgresql://user:pass@host:5432/db',
pool_size=10, # 连接池大小
max_overflow=5, # 最大溢出连接数
pool_timeout=30, # 等待连接超时(秒)
pool_recycle=3600, # 连接回收时间(秒)
pool_pre_ping=True # 连接前检查是否可用
)
几个关键点:
- pool_size 别设太大,10-20就够了。策略程序一般就几个线程,用不了那么多连接。
- pool_recycle 一定要设。数据库默认8小时断开空闲连接,不回收的话,第二天你会发现一堆死连接。
- pool_pre_ping 建议开启。每次从池里拿连接前先ping一下,避免拿到坏连接。
避坑指南:我曾经把pool_size设成50,结果数据库CPU飙升到100%。后来发现是连接太多,每个连接都在做空闲检查。改成10之后,一切正常。
数据表结构设计
表结构设计直接影响查询性能。我总结了几条经验:
1. 订单表
CREATE TABLE orders (
order_id VARCHAR(32) PRIMARY KEY,
strategy_id VARCHAR(16) NOT NULL,
symbol VARCHAR(10) NOT NULL,
side CHAR(1) NOT NULL, -- 'B' 买, 'S' 卖
order_type VARCHAR(8) NOT NULL, -- 'LIMIT', 'MARKET'
price DECIMAL(16,4),
quantity DECIMAL(16,4),
filled_qty DECIMAL(16,4) DEFAULT 0,
status VARCHAR(12) NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);
-- 常用查询索引
CREATE INDEX idx_orders_strategy ON orders(strategy_id, created_at);
CREATE INDEX idx_orders_symbol ON orders(symbol, created_at);
2. Tick行情表(时序)
-- ClickHouse 建表示例
CREATE TABLE tick_data (
symbol String,
timestamp DateTime64(3),
price Float64,
volume Float64,
bid_price Float64,
ask_price Float64
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(timestamp)
ORDER BY (symbol, timestamp);
注意这里用了 PARTITION BY 按月分区。为什么?因为Tick数据量太大,不分区的话,查询一个月的数据要扫描全表。分区后,查询只扫对应分区,速度快很多。
3. 策略运行日志表
CREATE TABLE strategy_logs (
log_id BIGSERIAL PRIMARY KEY,
strategy_id VARCHAR(16) NOT NULL,
log_level VARCHAR(8) NOT NULL, -- 'INFO', 'WARN', 'ERROR'
message TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- 按时间分区,方便清理旧日志
CREATE INDEX idx_logs_strategy_time ON strategy_logs(strategy_id, created_at);
小技巧:日志表建议定期清理。我一般保留30天数据,用定时任务删除旧记录。不然一张表存几年日志,查询会越来越慢。
知识体系结构图
下面这张图概括了数据库选型的核心逻辑:
嗯,数据库选型这块,说白了就是「看菜下饭」。别想着一个数据库打天下,该用MySQL就用MySQL,该上ClickHouse就上ClickHouse。我见过最惨的案例,有人用MySQL存Tick数据,三个月后查询一次要5分钟,最后不得不全量迁移。
记住:选型对了,后面省心十倍。