3. ClickHouse入门

在实时行情平台中,数据写入频率高、查询模式以“按标的+时间范围聚合”为主,传统行式数据库往往在压缩率与扫描效率上吃亏。ClickHouse 之所以成为行情类场景的热门选择,核心在于列式存储与 MergeTree 引擎家族的组合:前者让同列数据连续存放、压缩比高、扫描快,后者让写入与后台合并解耦,支撑高吞吐实时写入。

一、列式存储原理:为什么行情数据适合按列组织

行式存储把一行记录的所有字段连续写入磁盘,适合“按主键取整行”的 OLTP 场景;列式存储把同一列的值连续存放,适合“只读少数列、扫描大量行”的分析场景。行情数据中,查询通常只关心 symbol、price、volume、ts 等少数列,而 bid1..bid10、ask1..ask10 等大量列在多数查询中并不需要读取。列式存储让 ClickHouse 只加载相关列的数据块,I/O 量大幅下降。

列式存储还带来两个直接收益:一是同列数据类型一致,便于使用 Delta、DoubleDelta、Gorilla、LZ4、ZSTD 等编码与压缩算法,压缩比通常可达 5~20 倍;二是同列数据在磁盘上连续,向量化执行引擎可以按块批量处理,减少函数调用与分支预测开销。对于逐笔成交、盘口快照这类“写多读少、按时间范围聚合”的负载,列式存储几乎是天然匹配。

行式存储 vs 列式存储:行情表数据布局对比 行式存储(按行连续) ts=09:30:00 | symbol=600519 | price=1700.5 | volume=1200 ts=09:30:01 | symbol=600519 | price=1700.8 | volume=800 ts=09:30:02 | symbol=000001 | price=12.35 | volume=5000 读取 price 列仍需扫过整行,I/O 放大明显 列式存储(按列连续) ts 列 09:30:00 09:30:01 09:30:02 price 列 1700.5 1700.8 12.35 volume 1200 800 5000 只读 price 列时仅加载该列数据块,压缩与扫描效率更高 结论:行情查询多为“按 symbol + 时间范围聚合少数列”,列式存储减少 I/O、提升压缩比,并配合向量化执行加速聚合。 注意:列式存储不擅长单行频繁更新与点查,需通过 MergeTree 的排序键、分区与批量写入来扬长避短。

二、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();
8123 是 HTTP 端口,9000 是原生 TCP 端口。行情平台中,实时写入常用原生协议或 Kafka 引擎,BI 查询常用 HTTP 接口。
生产环境不要将数据目录放在容器可写层;应挂载独立磁盘并设置 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 * 读取宽表。复盘时应优先检查排序键、分区键、批量写入大小与后台合并状态,再考虑副本与分片扩展。