1、简介
影响ClickHouse查询性能的因素很多。在大多数情况下,关键因素
是ClickHouse在计算查询WHERE
子句条件时是否可以使用主键。因此,选择适用于最常见查询模式的主键对于有效的表设计至关重要。
然而,无论如何仔细地调整主键,都会不可避免地有查询用例来无法有效地使用它。用户通常依赖ClickHouse来获取时间序列类型的数据,但他们通常希望根据其他业务维度分析相同的数据,如客户id、网站URL或产品编号。在这种情况下,查询性能可能会更糟糕,因为需要对每个列值进行完整的扫描,才能应用WHERE子
句条件。虽然ClickHouse在这些情况下仍然相对较快,但评估数百万或数十亿的单独的数值会导致“非索引”的查询,比基于主键的查询要慢得多。
在传统的关系数据库中,有一种方法是将一个或多个“辅助”索引附加到一个表中。这是一个b树结构,允许数据库在O(log(n))
时间中找到所有匹配的行(log(n)
)时间,而不是O(n)
时间(一个表扫描),其中n是行数。然而,这种类型的辅助索引不会用于ClickHouse(或其面向列的数据库),因为磁盘上没有单独的行来添加索引。
相反,ClickHouse提供了一种不同类型的索引,在特定情况下可以显著提高查询速度。这些结构被标记为“跳数
”索引,因为它们使ClickHouse能够跳过读取保证没有匹配值的相当数量的数据块。
2、基本操作
用户只能在MergeTree
表族上使用跳数索引。每个数据跳转有四个主要参数:
- 索引名称。索引名用于在每个分区中创建索引文件。此外,在删除或具体化(materializing)索引时需要将其作为参数。
- 索引表达式。索引表达式用于计算存储在索引中的值集。它可以是列、简单运算符及(或)由索引类型决定的函数子集的组合
- TYPE。索引的类型控制着确定是否可以跳过读取和求值每个索引块的计算。
- 粒度(GRANULARITY)。每个索引块由粒度组成。例如,如果主表索引的粒度为8192行,索引粒度为4,则每个索引的“块”将是32768行。
当用户创建跳数索引时,每个数据部分目录中将有两个额外的文件用于表。
skp_idx_{index_name}.idx
包含有序的表达式值skp_idx_{index_name}.mrk2
包含到关联数据列文件的相应偏移量。
如果WHERE子
句过滤条件的某些部分在执行查询和读取相关列文件时匹配跳数索引表达式,ClickHouse将使用索引文件数据来确定是否必须处理或可以绕过每个相关的数据块(假设该块尚未通过应用主键排除)。要使用一个非常简化的示例,请考虑下面这个加载了可预测数据的表。
CREATE TABLE skip_table
(
my_key UInt64,
my_value UInt64
)
ENGINE MergeTree primary key my_key
SETTINGS index_granularity=8192;
INSERT INTO skip_table SELECT number, intDiv(number,4096) FROM numbers(100000000);
在执行一个不使用主键的简单查询时,扫描my_value
列中的所有1亿条目:
SELECT * FROM skip_table WHERE my_value IN (125, 700);
现在添加一个非常基本的跳数索引:
ALTER TABLE skip_table ADD INDEX vix my_value TYPE set(100) GRANULARITY 2;
通常跳数索引只应用于新插入的数据,所以仅仅添加索引不会影响上面的查询。
要索引已经存在的数据,使用这个语句:
ALTER TABLE skip_table MATERIALIZE INDEX vix;
使用新创建的索引重新运行查询:
SELECT * FROM skip_table WHERE my_value = 125
ClickHouse并没有处理1亿行800兆字节的数据,而是只读取和分析了16380行262KB的数据——2个8192行的粒度。
在一个更直观的形式中,这是如何读取和选择my_value
为125的4096行,以及如何跳过以下行而不从磁盘读取:
用户可以通过在执行查询时启用跟踪来访问有关跳过索引使用情况的详细信息。从clickhouse-client,设置send_logs_level
:
SET send_logs_level='trace';
这将在尝试调优查询SQL和表索引时提供有用的调试信息。从上面的例子中,调试日志显示跳跃索引删除了除两个颗粒外的所有颗粒:
<Debug> executeQuery: (from