4. 数据建模

在ClickHouse中做金融时序数据平台,表结构设计不是“建一张表”那么简单,而是把查询模式、写入模式、存储成本与数据生命周期四件事一次性对齐。金融时序数据通常具备高吞吐写入、按时间范围查询、按标的聚合、历史数据冷热分层等特征,因此建模的核心是:用分区键控制数据裁剪,用排序键控制索引与扫描效率,用主键控制去重与稀疏索引,用TTL控制数据生命周期。

核心原理:四件套各司其职

ClickHouse的MergeTree家族表引擎中,分区键、排序键、主键和TTL共同决定数据在磁盘上的组织方式。分区键决定数据被切分成多少个“分区目录”,查询时能直接跳过无关分区;排序键决定分区内数据按什么顺序排列,直接影响稀疏索引的过滤效率;主键是排序键的前缀,用于构建稀疏索引并支持去重;TTL则定义数据在何时被删除、移动到冷存储或聚合降采样。

关键关系:主键必须是排序键的前缀;排序键必须是分区键中表达式可用的列组合;TTL可以作用于列、分区或整表。四者顺序设计错误,会导致查询扫描量放大数倍甚至数十倍。

金融时序表建模四件套:分区 → 排序 → 主键 → TTL 分区键 PARTITION BY toYYYYMM(trade_date) 按时间裁剪分区 排序键 ORDER BY (symbol, trade_date, ts) 决定扫描顺序 主键 PRIMARY KEY (symbol, trade_date) 稀疏索引 + 去重 TTL TTL trade_date + INTERVAL 3 YEAR 冷热分层与删除 数据生命周期分层策略 热数据 0-3月 本地SSD,全字段 温数据 3-12月 本地HDD,压缩 冷数据 1-3年 对象存储,降采样 过期 >3年 TTL自动删除

方法流程:从查询模式反推表结构

金融时序建模不能先写DDL再想查询,而应遵循“查询模式 → 分区粒度 → 排序键顺序 → 主键前缀 → TTL策略”的流程。以下以A股逐笔成交表为例:

  1. 明确查询模式:最常见查询是“某标的某日全部成交”“某标的某时间段成交”“某日全市场成交额聚合”。
  2. 选择分区粒度:按toYYYYMM(trade_date)月分区,兼顾分区数量与裁剪效率;若数据量极大可按日分区。
  3. 设计排序键:将等值过滤列放前,范围过滤列放后,即(symbol, trade_date, ts)。
  4. 确定主键:取排序键前缀(symbol, trade_date),既保证稀疏索引有效,又支持ReplacingMergeTree按标的和日期去重。
  5. 配置TTL:热数据保留3个月,温数据12个月,冷数据3年后删除,并配合TO VOLUME实现冷热分层。
CREATE TABLE market.tick_trade
(
    symbol        LowCardinality(String),
    trade_date    Date,
    ts            DateTime64(3),
    price         Decimal(18, 4),
    volume        UInt64,
    amount        Decimal(24, 4),
    exchange      LowCardinality(String),
    ingest_time   DateTime DEFAULT now()
)
ENGINE = ReplacingMergeTree(ingest_time)
PARTITION BY toYYYYMM(trade_date)
ORDER BY (symbol, trade_date, ts)
PRIMARY KEY (symbol, trade_date)
TTL trade_date + INTERVAL 3 MONTH TO VOLUME 'hot',
    trade_date + INTERVAL 12 MONTH TO VOLUME 'warm',
    trade_date + INTERVAL 3 YEAR DELETE
SETTINGS index_granularity = 8192;

案例场景:逐笔成交与分钟K线双表设计

实际平台中,逐笔成交与分钟K线的查询模式差异很大。逐笔表强调“按标的+时间范围”扫描,排序键以symbol开头;分钟K线表强调“按时间+标的”聚合,且常按交易日全市场扫描,排序键可设计为(trade_date, symbol, minute)。两者分区键都可用月分区,但TTL策略不同:逐笔数据量大、价值衰减快,保留3个月热数据即可;分钟K线可保留3年用于回测。

表类型分区键排序键主键TTL策略
逐笔成交toYYYYMM(trade_date)(symbol, trade_date, ts)(symbol, trade_date)3月转冷,3年删除
分钟K线toYYYYMM(trade_date)(trade_date, symbol, minute)(trade_date, symbol)1年转冷,5年删除
日频指标toYYYY(calc_date)(symbol, calc_date)(symbol, calc_date)永久保留,按年归档

误区与复盘:常见建模陷阱

误区一:分区键粒度过细。按toYYYYMMDD日分区,一年产生365个分区,元数据膨胀,小分区合并压力大。建议月分区或周分区。

误区二:排序键顺序颠倒。把ts放在symbol前面,导致按标的查询无法利用稀疏索引,扫描量剧增。

误区三:主键与排序键不一致。主键不是排序键前缀时,ClickHouse会报错或索引失效,必须保证前缀关系。

误区四:TTL只写DELETE不写TO VOLUME。冷热分层缺失,所有数据挤在本地盘,成本与性能双输。

复盘建议:建表后用EXPLAIN indexes = 1验证查询是否命中分区裁剪与主键索引;用system.parts观察分区数量与大小;用system.moves确认TTL移动是否按预期执行。建模不是一次性的,应随查询模式变化定期复盘调整。

总结来看,金融时序表结构设计的本质是:分区键管“跳过多少数据”,排序键管“扫描多快”,主键管“索引多准”,TTL管“留多久、放哪里”。四者协同,才能在写入吞吐、查询延迟与存储成本之间取得平衡。