文章目录一、开篇一个颠覆认知的概念二、ClickHouse 主键的本质排序键 稀疏索引2.1 主键 排序键2.2 主键 稀疏索引的依据2.3 主键与 ORDER BY 的关系三、主键的作用四、主键索引的工作方式4.1 查询执行流程4.2 为什么不能精确定位到行五、主键设计的最佳实践5.1 把高频过滤列放在最前面5.2 主键不宜过多列5.3 高基数 vs 低基数列的顺序5.4 主键与分区键的配合六、常见误区与澄清七、与稀疏索引的关系八、总结一句话记住在 MySQL 中主键是每一行的“身份证”必须唯一且不能为空。但到了 ClickHouse你发现主键可以重复甚至可以是 NULL——这完全颠覆了传统认知。那么ClickHouse 的主键到底是什么它的作用是什么本文将彻底讲清 ClickHouse 主键索引的本质、用法和最佳实践。一、开篇一个颠覆认知的概念如果你从 MySQL 转过来第一次看到 ClickHouse 的主键定义大概率会困惑-- MySQL 的主键必须唯一不能为空CREATETABLEuser(idINTPRIMARYKEY,nameVARCHAR(100));-- ClickHouse 的主键可以重复可以为 NULLCREATETABLEuser_log(event_timeDateTime,user_id UInt64,event_type String)ENGINEMergeTree()PRIMARYKEY(event_time,user_id);-- 允许重复值核心区别对比项MySQL 主键ClickHouse 主键唯一性✅ 强制唯一❌ 允许重复作用唯一标识一行决定磁盘排序顺序 稀疏索引依据能否为 NULL❌ 不能✅ 能索引类型密集索引B-Tree稀疏索引与 ORDER BY 关系独立紧密关联可等价二、ClickHouse 主键的本质排序键 稀疏索引在 ClickHouse 的MergeTree引擎中PRIMARY KEY和ORDER BY关系非常紧密甚至经常被混用。2.1 主键 排序键数据在磁盘上按主键列的顺序物理存储。这是 ClickHouse 主键最核心的作用。-- 数据会先按 event_time 排序再按 user_id 排序PRIMARYKEY(event_time,user_id)效果event_time相近的行挨在一起同一时间点的user_id也有序2.2 主键 稀疏索引的依据ClickHouse 的主键索引是稀疏索引每隔 8192 行记录这一行的主键值到索引文件。┌─────────────────────────────────────────────────────────────┐ │ Granule 0行1~8192event_time 2025-01-01 10:00:00 │ │ Granule 1行8193~16384event_time 2025-01-01 10:01:00│ │ Granule 2行16385~24576event_time 2025-01-01 10:02:00│ └─────────────────────────────────────────────────────────────┘注意稀疏索引不记录每一行只记录每个 granule 的起始行的主键值。2.3 主键与 ORDER BY 的关系写法效果说明只写ORDER BYORDER BY即主键ClickHouse 会将其同时作为主键同时写ORDER BY和PRIMARY KEYPRIMARY KEY必须是ORDER BY的前缀元数据可冗余但排序仍按ORDER BY只写PRIMARY KEYORDER BY默认等于PRIMARY KEY主键即排序键-- 以下三种写法等价CREATETABLEt1(id UInt64,name String)ENGINEMergeTree()ORDERBYid;CREATETABLEt2(id UInt64,name String)ENGINEMergeTree()PRIMARYKEYid;CREATETABLEt3(id UInt64,name String)ENGINEMergeTree()ORDERBYidPRIMARYKEYid;三、主键的作用作用说明示例数据排序相同主键前缀的行物理上相邻提高压缩率加速范围查询稀疏索引快速跳过不相关的 granuleWHERE event_time 2025-01-01快速定位起始 granule数据去重配合ReplacingMergeTree使用需ORDER BY去重键必须是ORDER BY的前缀分区内排序每个分区内独立排序减少跨分区数据混叠注意ClickHouse 主键不保证唯一性也不会阻止插入重复值。如果需要去重需使用ReplacingMergeTree表引擎。四、主键索引的工作方式4.1 查询执行流程以WHERE event_time 2025-01-01 10:01:30为例步骤操作说明1二分查找索引在索引中找到event_time 目标值的第一个 granule — Granule 12定位到 granule根据索引条目定位到 Granule 1 在磁盘上的位置3块内扫描在 Granule 18192行内逐行扫描找到匹配的行结论主键索引帮你快速跳过不相关的 granule但 granule 内仍需线性扫描。4.2 为什么不能精确定位到行因为 ClickHouse 是列式存储 压缩。如果索引精确定位到行需要解压并读取该行所有列反而更慢。块内扫描可以批量解压、向量化执行效率更高。五、主键设计的最佳实践5.1 把高频过滤列放在最前面-- ✅ 好的设计查询总是带 event_datePRIMARYKEY(event_date,user_id)-- ❌ 差的设计user_id 在第一位但查询很少用它过滤PRIMARYKEY(user_id,event_date)5.2 主键不宜过多列-- ❌ 过多列PRIMARYKEY(col1,col2,col3,col4,col5)-- ✅ 1-3 列最佳PRIMARYKEY(event_date,user_id)原因主键列越多排序开销越大索引粒度越粗块内扫描范围越大。5.3 高基数 vs 低基数列的顺序查询模式推荐顺序原因等值查询为主WHERE user_id 123高基数在前快速缩小范围范围查询为主WHERE event_date 2025-01-01低基数在前范围字段放前面利用有序性5.4 主键与分区键的配合-- 按天分区按时间用户排序PARTITIONBYtoYYYYMMDD(event_date)PRIMARYKEY(event_date,user_id)效果分区裁剪 → 只扫描相关分区主键索引 → 快速定位分区内的 granule六、常见误区与澄清误区真相“主键必须唯一”❌ ClickHouse 主键允许重复甚至全是 NULL“主键是行级标识”❌ ClickHouse 没有行级唯一标识的概念“有了主键点查就快”⚠️ 主键帮助跳过 granule但 granule 内仍需扫描 8192 行“主键越多越好”❌ 主键列过多会导致排序开销大索引粒度变粗“PRIMARY KEY 和 ORDER BY 可以完全不同”⚠️ 可以不同但PRIMARY KEY必须是ORDER BY的前缀七、与稀疏索引的关系概念关系说明主键决定了按什么列排序是数据物理顺序的定义稀疏索引基于主键建立的索引每隔 8192 行记录主键值到索引文件主键索引两者合称主键 稀疏索引共同构成一句话主键定义规则稀疏索引基于规则建立路标。八、总结问题答案ClickHouse 主键是什么排序键 稀疏索引依据决定数据的物理顺序与 MySQL 主键的区别不保证唯一、可为 NULL、不是行级标识主键的作用排序、压缩、稀疏索引、数据去重配合特定引擎主键能加速点查吗有限只能跳过 granule块内仍需扫描主键设计原则高频过滤列在前1-3 列最佳PRIMARY KEY 和 ORDER BY 的关系紧密相关PRIMARY KEY必须是ORDER BY的前缀一句话记住ClickHouse 的主键不是“唯一身份证”而是“排序规则 稀疏索引依据”。它决定了数据在磁盘上的物理顺序帮助快速跳过不相关的数据块但不保证唯一性也不精确定位到行。如需深入了解 ClickHouse 的稀疏索引原理、ORDER BY 设计、分区策略等内容请持续关注本专栏《ClickHouse 一站式从入门到实战》系列文章。