订阅更新

ClickHouse 的分区键、排序键和主键怎么设计?

面试问 101 约 3 分钟读完 1 次阅读

本文目录
  1. 面试怎么回答
  2. 分区键:首先服务数据管理
  3. 排序键:决定数据在磁盘上的相邻关系
  4. 主键:稀疏索引,不默认保证唯一
  5. 怎么设计?从查询画像倒推
  6. 一个典型场景

面试怎么回答

ClickHouse 中分区键主要用于数据生命周期、Part 管理和粗粒度裁剪,不应选高基数字段制造大量小分区;ORDER BY 决定 Part 内物理排序、局部性和压缩,是查询性能设计重点;PRIMARY KEY 形成稀疏索引,通常是排序键的前缀,并不默认保证唯一。我会根据高频过滤、范围、租户和保留策略设计,再用实际查询验证扫描 Granule、Part 数和合并压力。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

把业务库建表习惯直接搬到 ClickHouse,最容易出现两个误判:把分区当查询索引,把主键当唯一约束。

在 MergeTree 家族里,分区、ORDER BY 和 PRIMARY KEY 分别服务于不同目标,设计错了会同时影响查询、合并和运维。

分区键负责数据生命周期和粗粒度裁剪,排序键决定磁盘排列与局部性,主键形成稀疏索引;三者应从查询模式和写入维护共同设计。

分区键:首先服务数据管理

分区把数据 Part 组织到逻辑范围内,常见选择是按月或按天。它便于按时间删除、移动、冻结和重写,也能在查询带有分区条件时做粗粒度裁剪。

分区不宜过细。把用户 ID 这类高基数字段作为分区,可能产生大量小 Part,增加元数据、合并和调度压力。一般先问数据如何过期、如何回灌、单个分区有多大,而不是追求“分区越多越快”。

排序键:决定数据在磁盘上的相邻关系

ORDER BY 决定一个 Part 内数据如何排序。经常过滤的字段放在前缀,相关数据就更集中,稀疏索引和数据跳过更容易减少读取。

列顺序很重要。若常见条件是 tenant_id = ? AND event_date BETWEEN ...,以租户和日期组成前缀通常比把随机 UUID 放在最前面更有局部性。

同时,排序键还影响压缩、聚合和 ReplacingMergeTree 等引擎的去重语义。不能只从某一条 SQL 出发。

主键:稀疏索引,不默认保证唯一

MergeTree 的主键用于构建稀疏索引,通常与排序键相同;如果单独指定,主键必须是排序键的前缀。

它不是传统 OLTP 数据库中的唯一性约束。需要去重时,要理解具体表引擎、版本列、合并时机和查询语义,不能以为写了 PRIMARY KEY 就会拒绝重复行。

分区键、排序键和主键职责

怎么设计?从查询画像倒推

先收集高频过滤、聚合、时间范围、租户范围和数据保留要求;再选择稳定且不过细的分区;然后把高频等值过滤、常用范围和能形成局部性的字段放入排序键前缀。

不要机械地“低基数在前”或“时间永远第一”。最左前缀是否匹配真实查询、各字段选择性如何、查询能跳过多少 Granule,才是需要验证的。

ClickHouse Key 设计流程

一个典型场景

日志表按天分区,ORDER BY event_id。业务查询几乎都按服务、租户和时间过滤,但 event_id 随机且很少用于条件。结果即使裁剪到一天,仍要读取大量 Granule。

重新分析查询后,把稳定的租户、服务和时间组织为排序前缀,并控制分区大小,扫描量才明显下降。问题不是 ClickHouse 没索引,而是物理顺序和访问模式完全不匹配。

刷题进度保存在当前浏览器