2. ClickHouse基础入门

在分布式行情集群中,ClickHouse之所以能承载每秒百万级的行情快照写入与毫秒级历史回放查询,根源在于它从存储布局到执行引擎都为“批量列式分析”而设计。本节从列式存储原理切入,串联MergeTree引擎族、数据类型、表引擎选择与安装部署,帮助你建立可落地的ClickHouse基础认知。

一、列式存储原理:为什么行情场景天然适配

行情数据具有典型的“宽表、少列查询、高压缩比”特征。以逐笔成交表为例,字段可能包含时间戳、合约代码、最新价、成交量、买卖盘口等数十列,但一次查询往往只关心其中3~5列。行式存储需要把整行数据读入内存再过滤,而列式存储将同一列的值连续存放,查询时只读取涉及的列,I/O量可下降一个数量级。

列式存储还带来两个直接收益:一是同列数据类型一致,压缩算法(如LZ4、ZSTD、Delta、DoubleDelta)能获得极高压缩率,行情历史数据通常可压缩到原大小的1/5~1/10;二是向量化执行,ClickHouse按列批量处理数据块(Block),配合SIMD指令,单核扫描吞吐可达数GB/s。

核心结论:列式存储不是“把表竖过来存”,而是以列粒度组织数据块、压缩、索引与执行,使“读少列、扫多行”的分析型行情查询获得数量级加速。
行式存储 vs 列式存储:行情表读取路径对比 行式存储 ts | symbol | price | volume | bid | ask ts | symbol | price | volume | bid | ask ts | symbol | price | volume | bid | ask 查询 price 需读取整行 → I/O 放大 列式存储 ts symbol price volume 查询 price 只读该列 → I/O 精准、压缩率高 行情场景收益:单列扫描 + 向量化执行 + 高压缩比,历史回放查询延迟从秒级降至毫秒级。 代价:点更新、单行事务、频繁小批量写入不友好,需依赖批量写入与MergeTree合并机制。

二、MergeTree引擎族:ClickHouse的存储核心

MergeTree是ClickHouse最核心的表引擎家族,几乎所有生产表都基于它或其派生引擎。其基本工作模型是:写入时按主键排序生成不可变的小数据片段(Part),后台线程持续将小Part合并为大Part,合并过程中完成去重、聚合或替换等语义。

MergeTree家族的关键成员及适用场景如下:

引擎核心语义行情场景示例
MergeTree基础排序合并,无去重原始逐笔流水、日志类行情
ReplacingMergeTree按排序键保留最新版本合约最新状态、盘口快照去重
SummingMergeTree合并时对数值列求和按分钟聚合的成交量统计
AggregatingMergeTree合并时按聚合函数状态合并K线OHLC、VWAP等预聚合
CollapsingMergeTree按Sign列折叠抵消订单状态变更、撤单抵消

选择引擎时先问三个问题:数据是否需要去重?是否需要预聚合?是否需要支持状态撤销?若都不需要,直接用MergeTree;若需要按主键保留最新,用ReplacingMergeTree;若需要增量聚合,用AggregatingMergeTree或SummingMergeTree。

三、数据类型与表引擎选择要点

ClickHouse数据类型直接影响存储与查询性能。行情场景常用类型包括:

  • DateTime64(3/6/9):毫秒/微秒/纳秒时间戳,行情时间精度首选。
  • Decimal64/Decimal128:精确价格,避免Float精度丢失。
  • LowCardinality(String):合约代码、交易所等低基数列,显著降低内存与I/O。
  • FixedString(N):定长代码,如交易所编码。
  • Array/Tuple:盘口多档买卖价量,适合嵌套结构。
价格字段务必使用Decimal而非Float,否则在聚合与比较时会出现精度漂移;时间字段优先DateTime64,避免用Int64手工换算。

表引擎选择还需关注分区键与排序键。行情表通常按toYYYYMMDD(ts)或toStartOfHour(ts)分区,按(symbol, ts)排序,使同一合约的时间序列连续存储,提升范围查询效率。

CREATE TABLE market.tick (
  ts DateTime64(3),
  symbol LowCardinality(String),
  price Decimal64(4),
  volume UInt64,
  bid Array(Decimal64(4)),
  ask Array(Decimal64(4))
) ENGINE = MergeTree
PARTITION BY toYYYYMMDD(ts)
ORDER BY (symbol, ts)
TTL ts + INTERVAL 90 DAY;

四、安装部署与基本SQL操作

单机部署推荐使用官方APT/YUM仓库或Docker。以Ubuntu为例:

sudo apt-get install -y clickhouse-server clickhouse-client
sudo systemctl start clickhouse-server
clickhouse-client --query "SELECT version()"

分布式行情集群通常采用“分片+副本”拓扑:每个分片内配置2个副本,通过ReplicatedMergeTree实现副本同步,借助ZooKeeper或ClickHouse Keeper协调。配置片段示例:

<remote_servers>
  <market_cluster>
    <shard>
      <replica><host>ch01</host><port>9000</port></replica>
      <replica><host>ch02</host><port>9000</port></replica>
    </shard>
    <shard>
      <replica><host>ch03</host><port>9000</port></replica>
      <replica><host>ch04</host><port>9000</port></replica>
    </shard>
  </market_cluster>
</remote_servers>

基本SQL操作与标准SQL接近,但需注意ClickHouse的批量写入习惯:

-- 批量写入
INSERT INTO market.tick VALUES
('2025-01-02 09:30:00.123','IF2501',3850.2,120, [3849.8],[3850.6]);

-- 查询某合约某时段均价
SELECT symbol, avg(price) AS avg_price, sum(volume) AS total_vol
FROM market.tick
WHERE symbol='IF2501' AND ts BETWEEN '2025-01-02 09:30:00' AND '2025-01-02 10:00:00'
GROUP BY symbol;
常见误区:用单条INSERT逐笔写入行情,会导致Part过多、后台合并压力剧增。正确做法是客户端攒批(如每500~1000条或每100ms)后批量写入,并控制单批大小在1~10MB。

五、复盘:从原理到落地的检查清单

  1. 确认查询是否只涉及少数列,若是则列式存储收益最大。
  2. 确认是否需要去重或预聚合,据此选择Replacing/Aggregating/Summing引擎。
  3. 价格用Decimal,时间用DateTime64,低基数列用LowCardinality。
  4. 分区键按天或小时,排序键以“合约+时间”为主。
  5. 写入必须攒批,避免小Part风暴;监控system.parts与合并队列。
  6. 分布式部署优先ReplicatedMergeTree,副本数至少2,协调组件独立部署。