在适当的情况下使用数据跳过索引
数据跳过索引应在遵循先前最佳实践的情况下考虑,即类型经过优化、选择了良好的主键并利用了物化视图。
如果在理解其工作原理的基础上谨慎使用,这些类型的索引可以加速查询性能。
ClickHouse 提供了一种强大的机制,称为 data skipping indices,它可以大幅减少查询执行期间扫描的数据量,特别是在主键对特定过滤条件不有帮助的情况下。与依赖于行基础的二级索引(如 B-树)传统数据库不同,ClickHouse 是一种列式存储,不以支持此类结构的方式存储行位置。相反,它使用跳过索引,帮助避免读取保证不会匹配查询过滤条件的数据块。
跳过索引的工作原理是存储有关数据块的元数据,如最小/最大值、值集合或布隆过滤器表示,并在查询执行期间使用这些元数据来确定哪些数据块可以完全跳过。它们仅适用于 MergeTree family 的表引擎,并使用表达式、索引类型、名称和定义每个索引块大小的粒度进行定义。这些索引与表数据一起存储,并在查询过滤器匹配索引表达式时进行查询。
有几种类型的数据跳过索引,每种索引适用于不同类型的查询和数据分布:
- minmax: 跟踪每个块中表达式的最小和最大值。适用于松散排序数据的范围查询。
- set(N): 跟踪每个块中指定大小 N 的值集合。对于每块具有低基数的列效果显著。
- bloom_filter: 概率性地确定值是否存在于某个块中,允许快速近似过滤集合成员资格。对于寻找“针”的查询效果显著,其中需要正面匹配。
- tokenbf_v1 / ngrambf_v1: 专门为在字符串中搜索标记或字符序列而设计的布隆过滤器变体 - 特别适用于日志数据或文本搜索用例。
虽然功能强大,但跳过索引必须小心使用。它们仅在消除大量数据块时提供好处,如果查询或数据结构不一致,可能会引入开销。如果一个块中存在一个匹配值,则仍然必须读取整个块。
有效的跳过索引使用通常依赖于索引列与表的主键之间的强相关性,或者以将相似值分组的方式插入数据。
一般来说,在确保正确的主键设计和类型优化后,最佳应用数据跳过索引。它们特别有效于:
- 整体基数高但每个块内基数低的列。
- 对搜索至关重要的稀有值(例如错误代码、特定 ID)。
- 在具有局部分布的非主键列上进行过滤的情况。
始终:
- 在真实数据和真实查询上测试跳过索引。尝试不同的索引类型和粒度值。
- 使用工具如 send_logs_level='trace' 和
EXPLAIN indexes=1
评估它们的影响以查看索引的有效性。 - 始终评估索引的大小以及粒度的影响。减少粒度大小通常会在某种程度上提高性能,导致更多的颗粒被过滤并需要被扫描。然而,随着粒度降低,索引大小增加,性能也可能退化。测量不同粒度数据点的性能和索引大小。这对于布隆过滤器索引尤其重要。
在适当使用时,跳过索引可以显著提升性能 - 而盲目使用它们可能会增加不必要的成本。
有关数据跳过索引的更详细指南,请参见 这里。
示例
考虑以下优化的表。此表包含每个帖子一行的 Stack Overflow 数据。
该表经过优化,适用于按帖子类型和日期过滤和聚合的查询。假设我们希望计算自 2009 年以来查看次数超过 10,000,000 的帖子数量。
此查询能够使用主索引排除一些行(和颗粒)。然而,大多数行仍需读取,如上述响应和以下 EXPLAIN indexes=1
所示:
简单分析显示 ViewCount
与 CreationDate
(主键)之间存在相关性,正如预期的那样 - 帖子存在越久,它被查看的时间就越长。
因此,这使得使用数据跳过索引成为逻辑选择。鉴于数字类型,使用 min_max 索引是合理的。我们使用以下 ALTER TABLE
命令添加索引 - 首先添加它,然后“物化”它。
该索引也可以在初始表创建时添加。带有 min max 索引的 DDL 定义架构:
以下动画展示了我们的 minmax 跳过索引如何为示例表构建,跟踪表中每块行(颗粒)的最小和最大 ViewCount
值:

重复我们之前的查询显示出显著的性能改善。注意扫描的行数减少:
EXPLAIN indexes=1
确认使用了该索引。
我们还展示了一个动画,展示 minmax 跳过索引如何修剪在我们的示例查询中不可能包含 ViewCount
> 10,000,000 谓词的所有行块:
