ClickHouse高性能实时分析数据库-稀疏索引

告别等待,秒级响应!这不只是教程,这是你驾驭PB级数据的超能力!我的ClickHouse视频课,凝练十年实战精华,从入门到精通,从单机到集群。点开它,让数据处理速度快到飞起,让你的职业生涯从此开挂!

全套视频教程联系博主 :试听视频位置

1 什么是稀疏索引?

首先,让我们忘掉传统数据库(如 MySQL)中的 B+Tree 索引。传统索引非常"稠密",它几乎为表中的每一行数据都建立一个索引条目,这使得它能快速定位到单行数据,但代价是索引本身会占用大量磁盘和内存空间,并且在写入新数据时需要复杂的维护(如页面分裂、旋转、合并)。

它是一种非常轻量级的索引,其核心思想是:不记录每一行数据的索引,只为每个数据块(Granule)的第一行数据建立索引条目。

这就好比一本书的目录:

  • 稠密索引(MySQL) :像是书末尾的"索引表",列出了几乎所有重要名词和它出现的具体页码(例如:ClickHouse: 5, 12, 23, 41...)。

  • 稀疏索引(ClickHouse) :像是书开头的"章节目录",只告诉你每一章从哪一页开始(例如:第一章: 第1页, 第二章: 第25页, 第三章: 第50页)。

当你需要找一个特定内容时,你先看目录(稀疏索引)找到它可能在的章节,然后你只需要阅读那一章(数据块),而不需要翻遍整本书。

2 核心概念: Granule index_granularity

要理解稀疏索引,必须先理解 Granule (数据颗粒/数据块) 这个概念。

  • Granule :是 ClickHouse 中数据存储和索引的基本单位。一个表的数据在物理上被分割成许多个 Granules

  • index_granularity :这是一个表设置,定义了每个 Granule 大约包含多少行数据。默认值是 8192 。这意味着 ClickHouse 在写入数据时,会以大约 8192 行为一个单位,将它们打包成一个 Granule

稀疏索引 就是为每个 Granule 创建一个索引条目,该条目记录了此 Granule 中主键列的 起始值 和它在物理文件中的 偏移量

3 图解稀疏索引的结构

假设我们有一张表,按 (EventDate, UserID) 作为主键排序,并且 index_granularity 设置为 4(为了方便演示,实际是8192)。

表数据如下:

|-----------|--------|----------|
| EventDate | UserID | Action |
| 2023/10/1 | 101 | login |
| 2023/10/1 | 105 | view |
| 2023/10/2 | 102 | click |
| 2023/10/2 | 109 | purchase |
| 2023/10/3 | 101 | view |
| 2023/10/3 | 103 | logout |
| 2023/10/4 | 110 | view |
| 2023/10/4 | 112 | click |

ClickHouse 在存储时,会这样做:

  1. 数据文件 ( .bin ):所有列的数据被连续存储。

  2. 主键索引文件 ( primary.idx ):存储稀疏索引。

下面是这个过程图示:

图解说明:

  • 左侧 data.bin :是实际存储数据的压缩文件。数据被分成了两个 Granule(因为我们的 index_granularity 假设为 4)。

  • 右侧 primary.idx:这就是稀疏索引文件。

    • Mark 0 : 记录了 Granule 0 的主键起始值 (2023-10-01, 101)

    • Mark 1 : 记录了 Granule 1 的主键起始值 (2023-10-03, 101)

  • 箭头 :表示索引中的每个"标记"(Mark)指向对应 Granuledata.bin 文件中的物理起始位置。

  1. 稀疏索引如何工作?(查询过程)

这是稀疏索引最神奇的地方。它通过排除法来大幅减少需要扫描的数据量。

查询示例: SELECT * FROM table WHERE EventDate = '2023-10-02'

查询处理流程如下:

流程详解:

  1. 扫描索引 (Step 1) : ClickHouse 加载 primary.idx 文件到内存(这个文件非常小),然后用查询条件 EventDate = '2023-10-02' 与索引中的每个标记进行比较。

  2. 确定范围 (Step 2):

    1. '2023-10-02' 大于等于 Mark 0 的起始日期 '2023-10-01'

    2. '2023-10-02' 小于 Mark 1 的起始日期 '2023-10-03'

    3. ClickHouse 推断出,所有满足条件的数据 如果存在,必定在 Granule 0 。因此,它完全不需要考虑 Granule 1 以及后续所有的数据块。这就是所谓的 "剪枝"(Pruning)

  3. 读取数据 (Step 3) : ClickHouse 根据 Mark 0 指向的偏移量,从 data.bin 文件中只读取 Granule 0 的数据(在本例中是4行,实际中是8192行)。

  4. 内存过滤 (Step 4) : 将解压后的 Granule 0 数据加载到内存中,然后进行逐行精确匹配,最终找到符合条件的2行数据。

通过这个过程,我们避免了全表扫描,也避免了读取 Granule 1,查询效率大大提升。

4 优点和最佳实践

优点:

  1. 极小的索引开销:由于每 8192 行才有一个索引条目,索引文件非常小,可以轻松缓存在内存中。

  2. 写入性能高 :写入新数据时,只需在文件末尾追加新的 Granule 和索引标记,几乎没有额外的索引维护成本。

  3. 对范围查询极其友好 :对于 WHERE time > '...' AND time < '...' 这样的范围查询,稀疏索引可以快速定位到包含这些时间的 Granule 范围,效率极高。

最佳实践(非常重要!):

  1. 主键选择和数据排序是关键!

    1. 稀疏索引的有效性 完全依赖于数据是按主键物理有序存储的

    2. 应该将查询中最常用的 低基数范围查询 的列放在主键的最前面。例如,对于日志分析,ORDER BY (EventDate, EventType, ...) 是一个非常好的主键选择。

    3. 如果你的数据没有按主键排序,稀疏索引将 完全失效,查询会退化为全表扫描。

  2. 合理设置 index_granularity

    1. 默认值 8192 通常是最佳选择,在大部分场景下无需修改。

    2. 较小的值(如 4096):索引更精确,可能需要读取的数据块更少,但索引文件会变大。

    3. 较大的值 (如 16384):索引文件更小,但每个 Granule 更大,可能导致查询时读取更多不必要的数据到内存中。


总结

ClickHouse 的稀疏索引是其高性能分析能力的基础。它通过牺牲单点查询的极致性能(与B+Tree相比),换来了极高的写入吞吐量、极低的索引维护成本和对范围扫描的卓越性能。

理解了 Granuleindex_granularity 和基于数据排序的"剪枝"工作原理,你就掌握了 ClickHouse 查询优化的核心精髓。记住,用好 ClickHouse 的第一步,就是设计好你的主键( ORDER BY 子句)

相关推荐
摸鱼仙人~2 小时前
Redis 数据结构全景解析
数据结构·数据库·redis
t198751284 小时前
解决MySQL删除/var/lib/mysql下的所有文件后无法启动的问题
数据库·mysql·adb
大佐不会说日语~7 小时前
Redis高频问题全解析
java·数据库·redis
会飞的灰大狼7 小时前
初识数据库
数据库
旋风菠萝9 小时前
JVM易混淆名称
java·jvm·数据库·spring boot·redis·面试
AWS官方合作商9 小时前
Amazon RDS for MySQL成本优化:RDS缓存降本实战
数据库·mysql·aws
77qqqiqi10 小时前
解决Property ‘sqlSessionFactory‘ or ‘sqlSessionTemplate‘ are required报错问题
java·数据库·微服务·mybatis·mybatisplus
眺望电子-ARM嵌入式10 小时前
技术笔记 | Ubuntu 系统 OTA 升级全流程详解
数据库·postgresql·php
程序猿小D11 小时前
Java项目:基于SSM框架实现的校园活动资讯网管理系统【ssm+B/S架构+源码+数据库+毕业论文+远程部署】
java·数据库·mysql·spring·毕业设计·ssm框架·校园活动