3. ClickHouse入门
在实时行情平台中,数据写入频率高、查询模式以“按标的+时间范围聚合”为主,传统行式数据库往往在压缩率与扫描效率上吃亏。ClickHouse 之所以成为行情类场景的热门选择,核心在于列式存储与 MergeTree 引擎家族的组合:前者让同列数据连续存放、压缩比高、扫描快,后者让写入与后台合并解耦,支撑高吞吐实时写入。
一、列式存储原理:为什么行情数据适合按列组织
行式存储把一行记录的所有字段连续写入磁盘,适合“按主键取整行”的 OLTP 场景;列式存储把同一列的值连续存放,适合“只读少数列、扫描大量行”的分析场景。行情数据中,查询通常只关心 symbol、price、volume、ts 等少数列,而 bid1..bid10、ask1..ask10 等大量列在多数查询中并不需要读取。列式存储让 ClickHouse 只加载相关列的数据块,I/O 量大幅下降。
列式存储还带来两个直接收益:一是同列数据类型一致,便于使用 Delta、DoubleDelta、Gorilla、LZ4、ZSTD 等编码与压缩算法,压缩比通常可达 5~20 倍;二是同列数据在磁盘上连续,向量化执行引擎可以按块批量处理,减少函数调用与分支预测开销。对于逐笔成交、盘口快照这类“写多读少、按时间范围聚合”的负载,列式存储几乎是天然匹配。
二、MergeTree引擎家族:写入、合并与查询的协作机制
MergeTree 是 ClickHouse 最核心的表引擎家族。它的基本思想是:每次批量写入生成一个按排序键有序的数据片段(part),后台线程持续将小 part 合并成大 part,查询时按分区与主键索引裁剪 part,只扫描必要的数据块。这样写入路径无需随机更新,合并路径负责整理数据,查询路径负责高效扫描。
MergeTree 家族常见成员及适用场景如下表所示:
| 引擎 | 核心能力 | 行情场景示例 |
|---|---|---|
| MergeTree | 基础有序存储、分区、主键索引、TTL | 逐笔成交明细、盘口快照原始表 |
| ReplacingMergeTree | 按排序键去重,保留版本号最大或最新行 | 标的静态信息、最新行情快照去重 |
| SummingMergeTree | 合并时对数值列求和 | 按分钟聚合的成交量、成交额汇总 |
| AggregatingMergeTree | 合并时按聚合函数状态合并 | 实时计算 VWAP、最大回撤等复杂指标 |
| CollapsingMergeTree | 通过 Sign 标记折叠旧状态 | 订单状态变更、撤单与成交的最终态 |
建表时排序键与分区键的选择直接决定查询性能。行情表通常以 (symbol, ts) 作为排序键,以 toYYYYMMDD(ts) 或 toStartOfHour(ts) 作为分区键。排序键让同一标的的数据连续,分区键让时间范围查询可以快速裁剪分区。
CREATE TABLE market.tick
(
symbol LowCardinality(String),
ts DateTime64(3),
price Decimal64(4),
volume UInt64,
bid1 Decimal64(4),
ask1 Decimal64(4)
)
ENGINE = MergeTree
PARTITION BY toYYYYMMDD(ts)
ORDER BY (symbol, ts)
TTL ts + INTERVAL 30 DAY
SETTINGS index_granularity = 8192;
三、安装部署:单机快速验证与生产注意事项
入门阶段建议先用 Docker 单机部署,快速验证建表、写入与查询链路。生产环境再考虑副本、分片与资源隔离。
docker run -d --name clickhouse-server \
-p 8123:8123 -p 9000:9000 \
-v /data/clickhouse:/var/lib/clickhouse \
clickhouse/clickhouse-server:24.3
# 进入客户端
docker exec -it clickhouse-server clickhouse-client
# 验证版本
SELECT version();
max_server_memory_usage、max_concurrent_queries 等参数,避免大查询拖垮写入。四、基本操作与常见误区复盘
建库建表后,可通过 INSERT 批量写入、SELECT 聚合查询、OPTIMIZE 手动合并、SYSTEM 命令查看 part 状态。行情写入应尽量批量,单次几千到几万行,避免逐行插入。
-- 批量写入
INSERT INTO market.tick VALUES
('600519', now64(3), 1700.5, 1200, 1700.4, 1700.6),
('000001', now64(3), 12.35, 5000, 12.34, 12.36);
-- 按标的与时间聚合
SELECT symbol, toStartOfMinute(ts) AS m,
argMax(price, ts) AS last_price,
sum(volume) AS vol
FROM market.tick
WHERE ts >= now() - INTERVAL 1 HOUR
GROUP BY symbol, m
ORDER BY m DESC
LIMIT 10;
-- 查看分区与 part
SELECT partition, name, rows, bytes_on_disk
FROM system.parts
WHERE table = 'tick' AND active = 1;
常见误区包括:把 ClickHouse 当 OLTP 数据库做单行更新;排序键选择不当导致扫描放大;分区粒度过细产生大量小 part;忽略 LowCardinality 与编码优化;以及用 SELECT * 读取宽表。复盘时应优先检查排序键、分区键、批量写入大小与后台合并状态,再考虑副本与分片扩展。