ClickHouse 如何用 skip index 加速过滤查询

7次阅读

clickhouse跳过索引需WHERE条件匹配索引表达式且类型适配,仅对新数据生效,须用EXPLaiN indexes=1和system.query_log验证是否真正跳块。

ClickHouse 如何用 skip index 加速过滤查询

跳过索引不是“加了就快”,得看 WHERE 条件能不能对上

ClickHouse 的 skip index 不是传统 B-tree 索引,它不加速单行定位,而是帮引擎跳过整块(granule)数据。所以它生效的前提很具体:查询的 WHERE 条件必须能被索引表达式“覆盖”,且索引类型要匹配过滤逻辑。

  • 比如建了 INDEX idx_user_id user_id TYPE set(100) GRANULARITY 2,那 WHERE user_id IN (1,2,3) 可能跳块;但 WHERE toString(user_id) LIKE '1%' 就完全用不上——表达式变了,索引失效
  • minmaxBETWEEN>= 有效,但对 !=NOT IN 基本无用
  • 索引只作用于新写入的数据;已有分区需显式执行 MATERIALIZE INDEX 才能生效

选对 TYPE 是关键,别乱套模板

不同 TYPE 解决不同问题,硬套会白费存储还拖慢写入:

  • minmax:适合时间戳、ID 等单调或近似单调字段,范围查询快;但若列值在 granule 内剧烈跳变(比如乱序插入的 user_id),min/max 范围太宽,跳块率骤降
  • set(N):适合枚举类、低基数字段(如 statuscountry_code);N 是每个 granule 最多存多少个去重值,超了整个 granule 就不索引——设太小没用,太大占空间
  • ngrambf_v1(n, size, hash, seed):文本模糊查必备,比如 WHERE page_url LIKE '%/api/v2/%';但注意它不支持正则,只对 n-gram 子串做布隆过滤,误判率存在,且 size 过小会导致漏跳

GRANULARITY 不是越大越好,得和主索引粒度对齐

ClickHouse 默认主索引粒度是 8192 行(index_granularity),而 skip index 的 GRANULARITY 指“几个粒度组成一个索引块”。设成 4,就是每 32768 行建一个索引项。

  • 设太小(如 GRANULARITY 1):索引文件暴涨,写入时 CPU 和磁盘压力明显上升,尤其高并发写入场景
  • 设太大(如 GRANULARITY 16):每个索引块覆盖行数过多,过滤精度下降,可能本该跳过的块没跳过去
  • 经验建议:从 GRANULARITY 24 起步;观察 system.partsmarksdata_compressed_bytes 变化,再微调

怎么确认 skip index 真正在干活?别只看执行时间

加速效果不能光比查询耗时,得看底层是否真跳了块。最直接的方式是查 EXPLAIN indexes = 1

EXPLAIN indexes = 1 SELECT * FROM events WHERE site_id = 123;

输出里如果出现 using primary key index + Using skipping index idx_site_id,说明双索引协同生效;若只有前者,说明 skip index 没触发。

  • 再进一步,查 system.query_log,过滤出该查询的 read_rowsread_bytes,对比加索引前后——跳块成功时,这两项应显著下降
  • 常见陷阱:WHERE 中用了函数包裹列(如 toYear(event_date)),但索引建在 event_date 上,此时索引失效;必须建表达式索引:INDEX idx_year toYear(event_date) TYPE minmax GRANULARITY 2

skip index 是把双刃剑:写入变重、存储略增、配置稍复杂,但它对高频过滤场景(尤其是高基数列 or 文本模糊查)的收益非常实在。真正难的不是“怎么建”,而是“建完后怎么验证它有没有在跳”——别让索引躺在表结构里睡大觉。

text=ZqhQzanResources