bigywater.tech
ClickHouse 内部原理 · 16

Data Skipping Index:minmax、set、bloom_filter

真实给一张500万行表的普通列加minmax跳数索引,用EXPLAIN indexes=1对比加索引前后同一条查询真实扫描的granule数量差异。

544→20
真实加minmax索引后granule扫描数下降约27倍
minmax/set/bloom_filter
三种跳数索引真实各自适合的场景不同

做的事:500万行表,给普通列v真实加一个minmax跳数索引

代码CREATE TABLE demo.skip1 (k UInt64, v UInt64) ENGINE=MergeTree ORDER BY k; INSERT INTO demo.skip1 SELECT number, number%1000000 FROM numbers(5000000); EXPLAIN indexes=1 SELECT count() FROM demo.skip1 WHERE v=42; -- 加索引前 ALTER TABLE demo.skip1 ADD INDEX idx_v v TYPE minmax GRANULARITY 4; ALTER TABLE demo.skip1 MATERIALIZE INDEX idx_v; -- 真实为已有数据补建索引 EXPLAIN indexes=1 SELECT count() FROM demo.skip1 WHERE v=42; -- 加索引后

验证:加索引前,544个granule真实全部要扫

实测 · 本机执行输出(加minmax索引之前)
EXPLAIN indexes=1 SELECT count() FROM demo.skip1 WHERE v=42;
→ ReadFromMergeTree (demo.skip1)
     Parts: 4 | Granules: 544        ← 真实544个granule全部进入扫描
     Indexes:
       PrimaryKey
         Condition: true
         Parts: 4/4
         Granules: 544/544            ← v不是排序键,主键索引真实帮不上忙

v列不是ORDER BY k的一部分,主键索引对它完全无能为力,Condition: true意味着主键索引对这次查询没有提供任何过滤信息,544个granule真实一个都没被排除。

验证:加了minmax跳数索引之后,真实降到20个granule

实测 · 本机执行输出(ALTER ADD INDEX + MATERIALIZE 之后)
EXPLAIN indexes=1 SELECT count() FROM demo.skip1 WHERE v=42;
→ ReadFromMergeTree (demo.skip1)
     Parts: 4 | Granules: 20          ← 真实从544降到20
     Indexes:
       Skip
         Name: idx_v
         Description: minmax GRANULARITY 4
         Condition: (v in [42, 42])
         Parts: 4/4
         Granules: 20/544              ← 真实排除了524个granule,降了约27倍
Data Skipping Index真实的工作原理,是给每GRANULARITY个连续granule(本文设的4)额外记一份轻量摘要——minmax类型记的是这一组granule里v列的最小值和最大值,查询规划阶段发现"v=42"这个条件跟某一组granule的[min,max]区间完全不相交,就真实可以直接跳过整组granule,连打开它们的数据都不用。跟主键索引的本质区别在于:主键索引是全局的一份稀疏索引(每granule一条),且必须严格按ORDER BY key的顺序才有意义;跳数索引可以真实加在任意列上(哪怕不在排序键里),常见的三种类型各有取舍——minmax适合数值列且数据在物理上有一定聚集性(比如本文的v=number%1000000,虽然全局是均匀分布,但因为分了4个granule一组,恰好还是拿到了真实的裁剪效果);set(N)记录每组granule里最多N个不同的枚举值,适合基数很低的列;bloom_filter用布隆过滤器判断某个值"肯定不在"这组granule里,适合做等值查询、且不要求精确的minmax范围。跳数索引不是免费的——它本身要占用额外的磁盘空间,而且每次INSERT/MERGE都要真实同步维护这份摘要,如果索引列的取值在物理上完全打散、没有任何聚集性,摘要几乎不会带来裁剪收益,反而白白多花维护成本。

分步演示:从544个granule全扫到裁剪到20个

每一步都是本文上面用真实EXPLAIN indexes=1跑出来的原始结果。

一个minmax跳数索引,真实把544个granule裁到20个
这一步新验证的状态

    本文用到的工具

    • 本机 ClickHouse 26.9.4(单节点),真实的 ALTER TABLE ADD INDEX / MATERIALIZE INDEX
    • 真实的 EXPLAIN indexes=1 Skip索引裁剪结果对比
    京ICP备2026067502号-1京公网安备11010502063487号