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 一站式从入门到实战》系列文章。

相关推荐
SelectDB技术团队2 天前
SelectDB 架构演进与 ClickHouse 迁移指南
大数据·数据库·clickhouse·云原生·架构·数据分析·apache doris
ClickHouseDB3 天前
使用 ClickHouse 与 Massive 构建实时市场数据应用
clickhouse
秉寒-CHO3 天前
从 ClickHouse 到 StarRocks:我在真实业务中怎么选明细、聚合、主键模型
大数据·clickhouse
IT界的老黄牛4 天前
ClickHouse 运维 SQL 速查:system 表查跑批、内存、合并与磁盘一篇通
大数据·clickhouse·磁盘占用·命令速查·运维排查·system表
wengad5 天前
podman安装clickhouse
clickhouse·podman
l1t8 天前
duckdb、clickhouse、datafusion、cedardb、tableauhyperapi 5种分析型数据库执行递归求数独SQL比较
数据库·sql·clickhouse
l1t8 天前
一个clickhouse易错的语法问题
数据库·sql·clickhouse
流烟默9 天前
ClickHouse 集群建表实战:从引擎选择到本地表 / 分布式表落地
分布式·clickhouse
深蓝电商API10 天前
亿级数据的存储选型:MongoDB/ES/ClickHouse横评
爬虫·clickhouse·mongodb·elasticsearch
流烟默12 天前
ClickHouse Part 机制与写入优化实践指南
大数据·运维·clickhouse