告别等待,秒级响应!这不只是教程,这是你驾驭PB级数据的超能力!我的ClickHouse视频课,凝练十年实战精华,从入门到精通,从单机到集群。点开它,让数据处理速度快到飞起,让你的职业生涯从此开挂!
全套视频教程联系博主 :试听视频位置
主键索引 (稀疏索引) 的工作原理
-
核心概念:稀疏索引 (Sparse Index)
与 MySQL 等数据库为每一行数据都建立索引(密集索引)不同,ClickHouse 的主键索引是稀疏的。它只为每个数据颗粒(Granule)的第一行记录一个“路标”。
-
数据颗粒 (Granule):ClickHouse 在存储数据时,会将表中的行分批打包,一个包就是一个 Granule。默认情况下,一个 Granule 包含 8192 行。
-
索引文件 (
primary.idx
):这个文件非常小,因为它只存储每个 Granule 的“路标”值。例如,如果ORDER BY
是(event_date)
,那么索引文件里存的就是每个 Granule 的起始日期。
图示
-
查询来了:
WHERE event_date = '2023-10-03'
。 -
扫描索引:ClickHouse 快速扫描内存中的
primary.idx
文件。 -
定位范围:它发现
'2023-10-03'
这个值介于路标2 ('2023-10-03'
) 和路标3 ('2023-10-05'
) 之间。这意味着,目标数据 只可能存在于 Granule 2 中。 -
精确打击:ClickHouse 直接跳过 Granule 1 和 Granule 3,只从磁盘读取 Granule 2 这一个数据块进行处理。
结论:稀疏索引的威力在于大幅减少 I/O。它不关心数据具体在哪一行,只关心数据在哪一个数据块范围内。
主键索引的设计要点:
-
列的选择:
ORDER BY
的列应该是你WHERE
子句中最常用的过滤条件,尤其是范围查询(>
,<
,BETWEEN
)。 -
列的顺序:把基数更高(筛选能力更强)的列放在前面。例如
ORDER BY (event_date, user_id)
就比ORDER BY (user_id, event_date)
要好,因为日期能先过滤掉大量不相关的数据块。
我们再强调一次:ClickHouse 的主键索引是稀疏的。它不像 MySQL 那样为每一行都建索引。它只为每个数据颗粒(Granule,默认8192行) 的第一行建立一条索引记录。
优点:索引文件非常小,可以常驻内存。 工作方式:查询时,ClickHouse 在内存中快速扫描索引,定位到可能包含目标数据的 Granule 范围,然后只把这些 Granule 从磁盘加载到内存中进行精确匹配。
【实践】: 为表添加跳数索引
给刚才的 user_behavior
表的 url
列添加一个布隆过滤器索引,以加速特定URL的查找。
-- 在建表时添加
CREATE TABLE user_behavior_with_index (-- ... 其他列定义和上面一样 ...url String,-- ...INDEX idx_url url TYPE bloom_filter() GRANULARITY 1 -- GRANULARITY表示索引的粒度
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, event_type, user_id);-- 查询时,ClickHouse会自动使用该索引
-- 这个查询会因为idx_url索引而变得更快
SELECT count()
FROM user_behavior_with_index
WHERE url = 'https://clickhouse.com/docs/en/';
数据跳过索引 (Skipping Indexes)-Granule 的“智能标签”
如果说主键索引是城市间的高速公路,那么数据跳过索引就是每个高速出口旁边的信息指示牌。它告诉你这个出口下去的区域“有什么”和“没有什么”,帮你决定是否要下高速。
数据跳过索引是附加在每个数据颗粒 (Granule) 上的元数据。它独立于主键索引,用于对非主键列进行预过滤。
除了主键,ClickHouse 还提供了额外的“跳数索引”,它们像给数据颗粒贴上的“标签”,进一步减少需要扫描的数据量。
-
minmax
: 记录每个颗粒内某列的最大最小值。如果查询WHERE price > 500
,而某个颗粒的minmax
标签是[100, 400]
,则可以直接跳过。 -
set(N)
: 记录每个颗粒内某列的前N个唯一值。如果查询WHERE color = 'Red'
,而某个颗粒的set
标签是{'Blue', 'Green'}
,则可以跳过。 -
bloom_filter
: 一种概率性索引。如果你查询WHERE has(urls, 'some_rare_url')
,布隆过滤器可以快速告诉你“这个颗粒绝对没有这个URL”,从而跳过。它可能会误报(说有但实际没有),但绝不会漏报。
① minmax
-
作用:记录每个 Granule 中某一列的最小值和最大值。
-
场景:非常适合数值或日期类型。
-
原理:查询
WHERE price > 1000
。如果某个 Granule 的minmax
标签是[100, 900]
,ClickHouse 就知道这个 Granule 内所有price
都小于等于900,不可能满足条件,于是直接跳过。 -
图解:查询 price > 1000
时,Granule 1 被直接跳过,因为它的最大值 900 都不满足条件。Granule 2 和 Granule 3 因为范围有交集,所以需要被读取。
② set(N)
-
作用:记录每个 Granule 中某列的前 N 个唯一值。
-
场景:适合基数较低的
String
或Enum
列,用于等值查询。 -
原理:查询
WHERE city = 'Shanghai'
。如果某个 Granule 的set(3)
标签是{'Beijing', 'Guangzhou', 'Shenzhen'}
,ClickHouse 就知道这个 Granule 里根本没有 'Shanghai',直接跳过。
③ bloom_filter
-
作用:一种概率性数据结构,可以非常确定地判断一个元素“绝对不存在”,但只能概率性地判断“可能存在”。
-
场景:
-
高基数的
String
列(如 URL,用户ID)。 -
检查数组中是否包含某个元素
has(array, 'value')
。 -
检查 Map 中是否存在某个键
mapContains(map, 'key')
。
-
-
原理:它像一个“黑名单筛选器”。数据写入时,把 Granule 里的值都扔进布隆过滤器。查询时,先问布隆过滤器:“这个值在你的黑名单上吗?”
-
如果回答“不在”(即绝对不存在),则安全跳过。
-
如果回答“可能在”(有可能是误报),则需要读取 Granule 进一步确认。
-
图解:查询 'e.com' 时,布隆过滤器 1 准确地告诉我们 Granule 1 中没有,从而避免了一次 I/O。布隆过滤器 2 提示可能存在,我们就需要去读取 Granule 2 来做最终的判断。
【实践】: 为表添加跳数索引
CREATE TABLE access_logs (event_time DateTime,request_id String,http_code UInt16,url String
) ENGINE = MergeTree()
ORDER BY (event_time)
SETTINGS index_granularity = 8192; -- 明确指定颗粒大小-- 为 request_id 和 http_code 添加跳数索引
ALTER TABLE access_logs ADD INDEX idx_req_id request_id TYPE bloom_filter() GRANULARITY 4;
ALTER TABLE access_logs ADD INDEX idx_code http_code TYPE set(0) GRANULARITY 4;
GRANULARITY 4
:表示这个跳数索引的粒度是主索引的 4 倍。即每 4 * 8192
行数据,才生成一个跳数索引块。这是一种在索引精度和大小之间的权衡。
2. 验证索引是否生效: 使用 EXPLAIN
或查询日志 system.query_log
是最好的方法。我们用一个更直观的方式:trace_logging
。
-- 执行带 trace_logging 的查询
SELECT count()
FROM access_logs
WHERE request_id = 'some-very-specific-request-id-abcdef'
SETTINGS log_queries=1; -- 确保查询被记录-- 在执行查询后,立刻查看日志
-- 在 clickhouse-server.log 文件中,或者在 system.query_log 表中查找
-- 你会看到类似这样的日志:
/*
<Trace> MergeTree(Reading): Mark ranges: [0, 1]
<Trace> MergeTree(Reading): Selected 1/100 parts by partition key
<Trace> MergeTree(Reading): Selected 1/50 ranges by primary key
<Trace> MergeTree(Reading): Selected 5/20 granules by skipping indexes -- 关键!
*/
日志中的 Selected ... granules by skipping indexes
明确告诉你,数据跳过索引生效了!它帮助 ClickHouse 在主键筛选之后,又进一步排除了更多的 Granule。
总结与最佳实践
-
主键索引是基石:
ORDER BY
决定了数据的大方向,是性能优化的第一道防线。 -
跳数索引是精细化武器:它在主键索引筛选后的“候选范围”内,进行二次精准打击,进一步减少 I/O。
-
按需索骥:不要滥用索引!每个索引都会在写入时带来额外的计算开销,并占用存储空间。只为那些真正能大幅缩小查询范围的列创建索引。
-
如何选择?
-
数值/日期范围查询 ->
minmax
-
低基数
String
/Enum
等值查询 ->set
-
高基数
String
等值查询或has()
/mapContains()
->bloom_filter
-
掌握了主键索引和数据跳过索引的组合拳,你就掌握了开启 ClickHouse 极致性能的钥匙。现在,去锻造你自己的“神兵利器”吧!