目录
1. 主键索引 (Primary Key Index) - 核心是稀疏索引
2. 跳数索引 (Data Skipping Indexes) - 二级索引
ClickHouse的索引设计哲学与其他传统OLTP数据库(如MySQL)有显著不同,它更侧重于高效扫描大数据集和快速过滤,而不是点查(Point Lookup)。其核心索引类型如下:
1. 主键索引 (Primary Key Index) - 核心是稀疏索引
-
- 原理: 这是ClickHouse最核心的索引机制。它不是传统意义上的B树索引。
- 数据结构: 本质上是一个稀疏索引。数据在物理存储上严格地按照主键(或ORDER BY键,两者在MergeTree引擎家族中概念等价)排序存储。
- 粒度: 索引并不是为每一行都建立条目,而是为每个数据部分的每个索引粒度(默认为8192行)记录该粒度内第一行的主键值(称为“标记”)。
- 工作方式:
-
-
- 当执行带有
WHERE
条件的查询(特别是涉及主键前缀或范围条件)时,ClickHouse利用这些稀疏的标记值。 - 它通过二分查找快速定位到可能包含目标数据的数据块(即索引粒度对应的数据段)。
- 然后,它只加载这些定位到的数据块(
.bin
文件中的相应部分)到内存中进行扫描和过滤,避免了全表扫描。
- 当执行带有
-
-
- 优势:
-
-
- 内存占用小: 稀疏索引占用的内存远小于稠密索引(如B树)。
- 范围查询极快: 对于按主键排序的范围查询(如时间范围),效率极高,只需加载少量相关数据块。
- 高效过滤: 能有效利用主键前缀进行过滤。
-
-
- 局限性:
-
-
- 点查效率较低: 查找单行数据时,可能仍需加载一个完整的数据块(8192行)进行扫描,不如B树高效。ClickHouse不是为高频点查设计的。
- 依赖主键顺序: 查询条件必须有效利用主键(或ORDER BY键)的前缀才能发挥最大效果。如果查询条件不涉及主键前缀,索引效果会大打折扣甚至无效。
- 非唯一约束: 声明主键不强制唯一性约束,它只定义数据的物理排序顺序和稀疏索引的构建依据。
-
-
- 存储位置: 索引标记存储在内存中(
primary.idx
文件在磁盘上,启动时加载到内存),查询时进行二分查找速度很快。
- 存储位置: 索引标记存储在内存中(
2. 跳数索引 (Data Skipping Indexes) - 二级索引
-
- 目的: 为了解决主键索引对非主键列(特别是高基数列或查询条件不涉及主键前缀时)过滤效率低下的问题。
- 原理: 在数据块(索引粒度)级别上存储关于该块内数据的摘要信息(Min/Max, Bloom Filter, 集合等)。查询时,先检查这些摘要信息,如果确定某个数据块内不可能包含满足查询条件的数据,则跳过整个数据块的读取。
- 核心思想: 跳过不需要扫描的数据块,减少IO和CPU消耗。
- 类型: ClickHouse提供了多种跳数索引类型,适用于不同的数据类型和查询模式:
-
-
minmax
: 存储每个数据块中指定列的最小值和最大值。适用于数值、日期等有序类型。如果查询条件范围与某个块的min-max范围无重叠,则跳过该块。最简单高效,通常首选。set
: 存储每个数据块中指定列的所有唯一值(或一个固定大小的超集)。适用于低中基数列的IN
或=
查询。如果查询值不在块的set中,则跳过该块。存储开销和构建成本随基数增大而增加。bloom_filter
: 为每个数据块中的指定列值构建一个布隆过滤器。适用于高基数列的IN
或=
查询,尤其是字符串。可以高效判断某个值“可能不存在”于该块(有一定误判率,但“存在”判断是准确的)。需要配置参数(false_positive
概率)。ngrambf_v1
/tokenbf_v1
: 基于N-gram或分词构建的布隆过滤器变种,专门优化字符串子串(LIKE
,%term%
) 和分词搜索的跳过。experimental
类型: 如hypothesis
索引(基于统计假设检验)等,用于特定高级场景。
-
-
- 创建: 在
CREATE TABLE
或ALTER TABLE
语句中声明。例如:
- 创建: 在
CREATE TABLE logs (
timestamp DateTime,
user_id UInt64,
url String,
...
INDEX idx_user_id user_id TYPE set(100) GRANULARITY 4,
INDEX idx_url_token url TYPE tokenbf_v1(32768, 3, 0) GRANULARITY 4
) ENGINE = MergeTree
ORDER BY (timestamp, user_id);
-
-
GRANULARITY
: 指定索引的粒度(覆盖多少个主键索引粒度)。GRANULARITY 1
表示每个跳数索引条目对应一个主键索引粒度(8192行)。GRANULARITY 4
表示一个跳数索引条目对应4个主键索引粒度(32768行)。更大的粒度可以减少索引大小但降低跳过精度。
-
-
- 优势:
-
-
- 显著加速对非主键列(尤其是高基数列)的过滤查询。
- 减少不必要的磁盘IO和CPU消耗。
-
-
- 局限性:
-
-
- 额外存储和计算开销: 需要存储索引数据,并在写入和合并时计算摘要信息。
- 可能无效: 如果数据分布使得摘要信息无法有效跳过块(例如所有块的值范围都很大且重叠),则索引效果差。
- 布隆过滤器误判:
bloom_filter
类型可能导致不必要的块读取(但不会漏读)。 - 粒度影响:
GRANULARITY
设置需要权衡索引大小和跳过精度。
-
3. 关键总结与最佳实践:
- 主键索引是基石: 表设计时,
ORDER BY
子句(即主键)的选择至关重要。优先选择最常用作过滤条件(尤其是范围过滤)的1-N个列。良好的主键设计能解决大部分高效查询问题。 - 跳数索引是补充: 当查询条件无法有效利用主键前缀或涉及高基数的非主键列时,考虑创建合适的跳数索引。
- 索引选择:
-
- 优先尝试
minmax
(对有序类型)。 - 对低基数的
IN
/=
用set
。 - 对高基数的
IN
/=
或字符串用bloom_filter
。 - 对
LIKE
/子串搜索用ngrambf_v1
/tokenbf_v1
。
- 优先尝试
- 评估与监控: 使用
EXPLAIN indexes = 1
查看查询是否使用了索引以及跳过了多少数据块。监控索引的存储开销和查询性能提升是否成正比。 - 理解ClickHouse哲学: ClickHouse索引的目标是最小化需要从磁盘读取的数据量,通过预排序(主键索引) 和 数据块摘要(跳数索引) 来实现。它不是为单行检索优化,而是为海量数据分析的聚合和扫描优化。
总而言之,ClickHouse的索引机制是其高性能查询的核心支撑之一。深刻理解主键(稀疏索引)和跳数索引的原理、适用场景以及局限性,是设计和优化ClickHouse表结构、编写高效查询的关键。