ClickHouse 主键索引详解:不是唯一标识,而是排序规则

文章目录

在 MySQL 中,主键是每一行的"身份证",必须唯一且不能为空。但到了 ClickHouse,你发现主键可以重复,甚至可以是 NULL------这完全颠覆了传统认知。那么,ClickHouse 的主键到底是什么?它的作用是什么?本文将彻底讲清 ClickHouse 主键索引的本质、用法和最佳实践。


一、开篇:一个颠覆认知的概念

如果你从 MySQL 转过来,第一次看到 ClickHouse 的主键定义,大概率会困惑:

sql 复制代码
-- MySQL 的主键:必须唯一,不能为空
CREATE TABLE user (
    id INT PRIMARY KEY,
    name VARCHAR(100)
);

-- ClickHouse 的主键:可以重复,可以为 NULL
CREATE TABLE user_log (
    event_time DateTime,
    user_id UInt64,
    event_type String
) ENGINE = MergeTree()
PRIMARY KEY (event_time, user_id);  -- 允许重复值

核心区别:

对比项 MySQL 主键 ClickHouse 主键
唯一性 ✅ 强制唯一 ❌ 允许重复
作用 唯一标识一行 决定磁盘排序顺序 + 稀疏索引依据
能否为 NULL ❌ 不能 ✅ 能
索引类型 密集索引(B-Tree) 稀疏索引
与 ORDER BY 关系 独立 紧密关联(可等价)

二、ClickHouse 主键的本质:排序键 + 稀疏索引

在 ClickHouse 的 MergeTree 引擎中,PRIMARY KEY 和 ORDER BY 关系非常紧密,甚至经常被混用。

2.1 主键 = 排序键

数据在磁盘上按主键列的顺序物理存储。这是 ClickHouse 主键最核心的作用。

sql 复制代码
-- 数据会先按 event_time 排序,再按 user_id 排序
PRIMARY KEY (event_time, user_id)

效果:

  • event_time 相近的行挨在一起
  • 同一时间点的 user_id 也有序

2.2 主键 = 稀疏索引的依据

ClickHouse 的主键索引是稀疏索引:每隔 8192 行,记录这一行的主键值到索引文件。

复制代码
┌─────────────────────────────────────────────────────────────┐
│  Granule 0(行1~8192):event_time = 2025-01-01 10:00:00    │
│  Granule 1(行8193~16384):event_time = 2025-01-01 10:01:00│
│  Granule 2(行16385~24576):event_time = 2025-01-01 10:02:00│
└─────────────────────────────────────────────────────────────┘

注意:稀疏索引不记录每一行,只记录每个 granule 的起始行的主键值。

2.3 主键与 ORDER BY 的关系

写法 效果 说明
只写 ORDER BY ORDER BY 即主键 ClickHouse 会将其同时作为主键
同时写 ORDER BY 和 PRIMARY KEY PRIMARY KEY 必须是 ORDER BY 的前缀 元数据可冗余,但排序仍按 ORDER BY
只写 PRIMARY KEY ORDER BY 默认等于 PRIMARY KEY 主键即排序键
sql 复制代码
-- 以下三种写法等价
CREATE TABLE t1 (id UInt64, name String) ENGINE = MergeTree() ORDER BY id;
CREATE TABLE t2 (id UInt64, name String) ENGINE = MergeTree() PRIMARY KEY id;
CREATE TABLE t3 (id UInt64, name String) ENGINE = MergeTree() ORDER BY id PRIMARY KEY id;

三、主键的作用

作用 说明 示例
数据排序 相同主键前缀的行物理上相邻 提高压缩率,加速范围查询
稀疏索引 快速跳过不相关的 granule WHERE 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 1
2 定位到 granule 根据索引条目,定位到 Granule 1 在磁盘上的位置
3 块内扫描 在 Granule 1(8192行)内逐行扫描,找到匹配的行

结论:主键索引帮你快速跳过不相关的 granule,但 granule 内仍需线性扫描。

4.2 为什么不能精确定位到行?

因为 ClickHouse 是列式存储 + 压缩。如果索引精确定位到行,需要解压并读取该行所有列,反而更慢。块内扫描可以批量解压、向量化执行,效率更高。


五、主键设计的最佳实践

5.1 把高频过滤列放在最前面

sql 复制代码
-- ✅ 好的设计:查询总是带 event_date
PRIMARY KEY (event_date, user_id)

-- ❌ 差的设计:user_id 在第一位,但查询很少用它过滤
PRIMARY KEY (user_id, event_date)

5.2 主键不宜过多列

sql 复制代码
-- ❌ 过多列
PRIMARY KEY (col1, col2, col3, col4, col5)

-- ✅ 1-3 列最佳
PRIMARY KEY (event_date, user_id)

原因:主键列越多,排序开销越大,索引粒度越粗,块内扫描范围越大。

5.3 高基数 vs 低基数列的顺序

查询模式 推荐顺序 原因
等值查询为主(WHERE user_id = 123) 高基数在前 快速缩小范围
范围查询为主(WHERE event_date > '2025-01-01') 低基数在前 范围字段放前面,利用有序性

5.4 主键与分区键的配合

sql 复制代码
-- 按天分区,按时间+用户排序
PARTITION BY toYYYYMMDD(event_date)
PRIMARY KEY (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 一站式从入门到实战》系列文章。

相关推荐
l1t2 天前
DeepSeek总结的clickhouse的二进制文件自解压机制
clickhouse
l1t2 天前
DeepSeek总结的pg_clickhouse 与 chdb 更新:编码、嵌套与类型
数据库·clickhouse·postgresql
拔剑纵狂歌5 天前
ClickHouse system.query_log 字段详解与排障实践
后端·clickhouse·1024程序员节
SelectDB技术团队5 天前
Apache Doris 高性能 Open Lake Variant 读写技术解析(含对比数据)
数据结构·数据库·clickhouse·日志·apache doris·selectdb·湖仓架构升级
SelectDB技术团队5 天前
ClickHouse 存日志的能力边界:并发、检索与 trace 回放的实测对照
数据结构·数据库·clickhouse·日志分析·apache doris·日志存储
SelectDB技术团队5 天前
Agent Trace 数据底座建设:宽表建模、全文检索与成本聚合的配置与验证步骤
大数据·python·clickhouse·elk·elasticsearch·全文检索·复杂查询
此时不提桶,更待何时6 天前
05-03-B-ClickHouse与OLAP面试与生产事故实战
clickhouse·面试·nosql
SelectDB技术团队6 天前
ELK 太占磁盘、ES 总报写入拒绝:从归因到可执行的优化清单
大数据·clickhouse·elk·elasticsearch·全文检索·复杂查询·实时更新
frjc7 天前
数据库选型:如何从众多数据库中选出最理想的那一个
redis·mysql·clickhouse·elasticsearch
dkgee14 天前
记录一次Clickhouse持续占用CPU超过100%问题分析
clickhouse·hermes