跳到主要内容
跳到主要内容

主键索引

寻找高级索引细节?

本页面介绍了 ClickHouse 的稀疏主索引,它是如何构建的,如何工作以及它如何加速查询。

有关高级索引策略和更深入的技术细节,请参见 主索引深入探讨

ClickHouse 中稀疏主索引如何工作?


ClickHouse 中的稀疏主索引帮助有效识别 granules—可能包含匹配查询条件的数据行块,这些条件基于表的 ^^主键^^ 列。在下一节中,我们将解释如何从这些列中的值构建该索引。

稀疏主索引创建

为了说明稀疏主索引是如何构建的,我们使用 uk_price_paid_simple 表并配合一些动画演示。

作为一个 提醒,在我们的 ① 示例表中,其 ^^主键^^ 为 (town, street),② 插入的数据是 ③ 存储在磁盘上,按 ^^主键^^ 列值排序并压缩,分别存储在每列的文件中:



在处理过程中,每列的数据被 ④ 逻辑上划分为 granules——每个 granule 覆盖 8,192 行——这是 ClickHouse 的数据处理机制所使用的最小单位。

这种 ^^granule^^ 结构也正是使主索引 稀疏 的原因:ClickHouse 仅从每个 ^^granule^^ 中存储一行的 ^^主键^^ 值——具体来说,就是第一行。这导致每个 ^^granule^^ 只有一个索引条目:



由于其稀疏性,主索引足够小,可以完全放入内存中,从而使基于 ^^主键^^ 列的查询谓词的快速过滤成为可能。在下一节中,我们将展示它如何加速这些查询。

主索引使用

我们用另一个动画简要说明稀疏主索引如何用于查询加速:



① 示例查询包含对两个 ^^主键^^ 列的谓词:town = 'LONDON' AND street = 'OXFORD STREET'

② 为了加速查询,ClickHouse 将表的主索引加载到内存中。

③ 然后扫描索引条目,以识别哪些 granules 可能包含匹配谓词的行——换句话说,哪些 granules 不能被跳过。

④ 这些潜在相关的 granules 随后被加载并 处理 到内存中,以及查询所需的其他列的相应 granules。

监控主索引

表中的每个 数据部分 都有其自己的主索引。我们可以使用 mergeTreeIndex 表函数检查这些索引的内容。

以下查询列出了我们示例表中每个数据部分的主索引条目数量:

SELECT
    part_name,
    max(mark_number) AS entries
FROM mergeTreeIndex('uk', 'uk_price_paid_simple')
GROUP BY part_name;
   ┌─part_name─┬─entries─┐
1. │ all_2_2_0 │     914 │
2. │ all_1_1_0 │    1343 │
3. │ all_0_0_0 │    1349 │
   └───────────┴─────────┘

此查询显示了当前一个数据 ^^parts^^ 的主索引的前 10 个条目。请注意,这些 ^^parts^^ 在后台持续被 合并 成更大的 ^^parts^^:

SELECT 
    mark_number + 1 AS entry,
    town,
    street
FROM mergeTreeIndex('uk', 'uk_price_paid_simple')
WHERE part_name = (SELECT any(part_name) FROM mergeTreeIndex('uk', 'uk_price_paid_simple')) 
ORDER BY mark_number ASC
LIMIT 10;
    ┌─entry─┬─town───────────┬─street───────────┐
 1. │     1 │ ABBOTS LANGLEY │ ABBEY DRIVE      │
 2. │     2 │ ABERDARE       │ RICHARDS TERRACE │
 3. │     3 │ ABERGELE       │ PEN Y CAE        │
 4. │     4 │ ABINGDON       │ CHAMBRAI CLOSE   │
 5. │     5 │ ABINGDON       │ THORNLEY CLOSE   │
 6. │     6 │ ACCRINGTON     │ MAY HILL CLOSE   │
 7. │     7 │ ADDLESTONE     │ HARE HILL        │
 8. │     8 │ ALDEBURGH      │ LINDEN ROAD      │
 9. │     9 │ ALDERSHOT      │ HIGH STREET      │
10. │    10 │ ALFRETON       │ ALMA STREET      │
    └───────┴────────────────┴──────────────────┘

最后,我们使用 EXPLAIN 子句查看所有数据 ^^parts^^ 的主索引如何用于跳过无法包含匹配示例查询谓词的行的 granules。这些 granules 被排除在加载和处理之外:

EXPLAIN indexes = 1
SELECT
    max(price)
FROM
    uk.uk_price_paid_simple
WHERE
    town = 'LONDON' AND street = 'OXFORD STREET';
    ┌─explain────────────────────────────────────────────────────────────────────────────────────────────────────┐
 1. │ Expression ((Project names + Projection))                                                                  │
 2. │   Aggregating                                                                                              │
 3. │     Expression (Before GROUP BY)                                                                           │
 4. │       Expression                                                                                           │
 5. │         ReadFromMergeTree (uk.uk_price_paid_simple)                                                        │
 6. │         Indexes:                                                                                           │
 7. │           PrimaryKey                                                                                       │
 8. │             Keys:                                                                                          │
 9. │               town                                                                                         │
10. │               street                                                                                       │
11. │             Condition: and((street in ['OXFORD STREET', 'OXFORD STREET']), (town in ['LONDON', 'LONDON'])) │
12. │             Parts: 3/3                                                                                     │
13. │             Granules: 3/3609                                                                               │
    └────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

请注意,上面的 EXPLAIN 输出中的第 13 行显示,从所有数据 ^^parts^^ 中,只有 3 个 granules 被主索引分析选择用于处理,剩余的 granules 被完全跳过。

我们还可以通过简单地运行查询来观察到大部分数据被跳过:

SELECT max(price)
FROM uk.uk_price_paid_simple
WHERE (town = 'LONDON') AND (street = 'OXFORD STREET');
   ┌─max(price)─┐
1. │  263100000 │ -- 263.10 million
   └────────────┘

1 row in set. Elapsed: 0.010 sec. Processed 24.58 thousand rows, 159.04 KB (2.53 million rows/s., 16.35 MB/s.)
Peak memory usage: 13.00 MiB.

如上所示,示例表中约 3000 万行中,仅处理了约 25,000 行:

SELECT count() FROM uk.uk_price_paid_simple;
   ┌──count()─┐
1. │ 29556244 │ -- 29.56 million
   └──────────┘

关键要点

  • 稀疏主索引 帮助 ClickHouse 跳过不必要的数据,通过识别哪些 granules 可能包含匹配 ^^主键^^ 列查询条件的行来实现。

  • 每个索引仅存储 每个 ^^granule^^ 的第一行 的 ^^主键^^ 值(默认情况下一个 ^^granule^^ 有 8,192 行),使其紧凑到足以放入内存中。

  • 每个 ^^MergeTree^^ 表中的 每个数据部分 都有其 自己的主索引,在查询执行过程中独立使用。

  • 在查询过程中,索引允许 ClickHouse 跳过 granules,减少 I/O 和内存使用,同时加速性能。

  • 您可以使用 mergeTreeIndex 表函数 检查索引内容,并使用 EXPLAIN 子句监控索引使用情况。

在哪里找到更多信息

若要深入了解 ClickHouse 中稀疏主索引的工作原理,包括它们与传统数据库索引的不同以及最佳实践,请查看我们的详细索引 深入探讨

如果您对 ClickHouse 如何以高度并行的方式处理主索引扫描选择的数据感兴趣,请查看查询并行性指南 在这里