ClickHouse 稀疏索引深度解析:为什么 OLAP 数据库不用 B-Tree?

文章目录

在 MySQL 中,我们习惯了 B-Tree 索引的"精准打击"------一条查询瞬间定位到行。但在 ClickHouse 里,你会发现索引完全不是一回事:没有 B-Tree,没有行级定位,甚至索引文件小得可以忽略不计。这就是稀疏索引。本文将深入解析稀疏索引的设计原理、工作方式,以及它为什么更适合 OLAP 场景。


一、开篇:一个常见的困惑

很多从 MySQL 转过来的开发者,第一次接触 ClickHouse 时会问:

"为什么我的 WHERE id = 123 查询还是慢?"

"为什么我建了索引,但好像没起作用?"

答案是:ClickHouse 的索引不是 B-Tree,而是稀疏索引。

这两种索引的设计目标完全不同:

对比项 MySQL(B-Tree 密集索引) ClickHouse(稀疏索引)
记录粒度 每行都记录一个索引项 每隔 N 行(默认 8192 行)记录一个索引项
索引大小 大(约数据量的 10%) 极小(约数据量的 0.1%)
定位精度 直接定位到具体行 定位到数据块(granule),块内扫描
适用场景 点查 WHERE id = 123 范围查询、扫描大量行
写入代价 高(需维护索引树) 低(只需追加索引条目)

二、什么是稀疏索引?

2.1 一句话定义

稀疏索引是一个"跳着看"的目录:每隔 8192 行,记录一下这一行数据的位置和主键值。

2.2 图解:稀疏索引的物理结构

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                              数据文件(按主键排序)                           │
├─────────────────────────────────────────────────────────────────────────────┤
│  Granule 0          │  Granule 1          │  Granule 2          │  ...     │
│  行1 ~ 行8192        │  行8193 ~ 行16384    │  行16385 ~ 行24576   │          │
│  主键值: 1~100       │  主键值: 101~200     │  主键值: 201~300     │          │
└─────────────────────────────────────────────────────────────────────────────┘
          │                        │                        │
          ▼                        ▼                        ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                              索引文件(稀疏索引)                            │
├─────────────────────────────────────────────────────────────────────────────┤
│  条目0: (主键最小值=1, 位置偏移=0)                                          │
│  条目1: (主键最小值=101, 位置偏移=8192行)                                    │
│  条目2: (主键最小值=201, 位置偏移=16384行)                                   │
│  ...                                                                        │
└─────────────────────────────────────────────────────────────────────────────┘

关键点

  • 索引不记录每一行,只记录每个 granule(8192 行)的起始位置主键最小值
  • 索引大小 ≈ 数据量 / 8192,不到数据量的 0.1%

三、稀疏索引如何工作?

3.1 查询执行流程

WHERE id = 105 为例:

sql 复制代码
SELECT * FROM table WHERE id = 105;
步骤 操作 说明
1 二分查找索引 在索引中找到 id >= 105 的第一个 granule --- 条目1(主键最小值=101)
2 定位到 granule 根据条目1的位置偏移,定位到 Granule 1(行8193~行16384)
3 块内扫描 在 Granule 1 内逐行扫描,找到 id = 105 的行

结论 :稀疏索引只能帮你快速跳过不相关的 granule,但无法精确到行。granule 内的扫描是线性的。

3.2 范围查询的优势

WHERE id BETWEEN 150 AND 250

步骤 操作
1 二分查找找到起始 granule(条目1,最小值=101)
2 从 Granule 1 开始,连续读取 Granule 2、Granule 3... 直到 id > 250
3 在读取的每个 granule 内过滤数据

优势:连续读取多个 granule,充分利用磁盘顺序读性能,非常高效。


四、为什么 ClickHouse 选择稀疏索引?

设计目标 为什么选稀疏索引
极低存储开销 索引大小 ≈ 数据量的 0.1%,几乎可以忽略
快速写入 无需维护复杂的 B-Tree 结构,直接追加索引条目
支持海量数据 10 亿行数据,索引只有约 12 万条记录
适合范围扫描 一次性读取连续 granule,磁盘顺序读性能极佳
列式扫描友好 granule 是列式压缩的基本单位,一次读取一个完整压缩块

代价

  • 点查性能差(需要 granule 内扫描)
  • 不适合高并发点查场景

五、密集索引 vs 稀疏索引:量化对比

假设一张 10 亿行、主键为 user_id 的表:

对比项 MySQL(B-Tree 密集索引) ClickHouse(稀疏索引)
索引条目数 10 亿条 ≈ 122,000 条(10亿 / 8192)
索引大小 ≈ 20GB ≈ 2MB
WHERE id = 123 3~4 次磁盘 I/O 定位 granule + 扫描 8192 行
WHERE id BETWEEN 1 AND 10000 回表 10000 次,极慢 连续读几个 granule,极快
写入维护成本 高(插入时更新 B-Tree) 低(追加)

核心结论 :稀疏索引是 ClickHouse 为海量数据扫描和范围查询做的刻意取舍------牺牲点查精度,换取极低的存储开销和极高的扫描性能。


六、优化稀疏索引的最佳实践

6.1 把高频过滤列放在 ORDER BY 最前面

sql 复制代码
-- ✅ 好的设计:查询总是带 event_date
ORDER BY (event_date, user_id)

-- ❌ 差的设计:user_id 在第一位,但查询很少用它过滤
ORDER BY (user_id, event_date)

6.2 避免点查,改用范围查询

sql 复制代码
-- ❌ 点查(需要 granule 内扫描)
WHERE user_id = 12345

-- ✅ 范围查询(连续 granule,性能更好)
WHERE user_id >= 12345 AND user_id < 12346

6.3 合理设置 index_granularity

sql 复制代码
CREATE TABLE table (
    ...
) ENGINE = MergeTree()
SETTINGS index_granularity = 16384;  -- 增大到 16384,索引更小,但块内扫描更大
index_granularity 索引大小 点查性能 范围查询性能
8192(默认) 基准 基准 基准
4096 2倍 更好 稍差(更多索引条目)
16384 减半 更差 更好(连续块更大)

七、常见误区与澄清

误区 真相
"ClickHouse 没有索引" ❌ 有稀疏索引,只是和 B-Tree 不同
"索引越大越好" ❌ 稀疏索引追求小,越小扫描越快
"点查也能很快" ⚠️ 可以,但需要配合分区裁剪和主键设计,且不如 MySQL
"ORDER BY 只用于排序" ❌ 它还决定了数据的物理顺序和稀疏索引的结构

八、总结

问题 答案
什么是稀疏索引? 每隔 8192 行记录一个索引项,定位到数据块而非具体行
为什么不用 B-Tree? B-Tree 太大,维护成本高,不适合 OLAP 的海量数据场景
点查怎么办? 尽量避免点查;如需点查,配合分区裁剪 + ORDER BY 优化
索引大小有多少? 约数据量的 0.1%,10 亿行数据索引仅 ~2MB
什么时候稀疏索引最有效? 范围查询、时间序列扫描、需要快速跳过大量数据块的场景

一句话记住

稀疏索引是一个"跳着看的目录":路标很少,但能帮你快速跳过大量无关数据。它适合 OLAP 的海量扫描,不适合 OLTP 的精确点查。


如需深入了解 ClickHouse 的部署架构选型、分片与副本机制详解、分布式表原理剖析、无中心架构设计哲学、生产环境集群调优、多副本一致性实践、ClickHouse Keeper 核心原理等内容,请持续关注本专栏《ClickHouse 一站式从入门到实战》系列文章。

相关推荐
ClouGence13 小时前
Oracle 数据同步为什么会出现数据不一致?长事务是常被忽略的原因
数据库·后端·oracle
飞将15 小时前
从零实现数据库(2)——HashIndex + IndexManager
数据库
Nturmoils1 天前
订单列表慢查询,先看 WHERE、ORDER BY 和 LIMIT
数据库
渣波2 天前
拒绝 SQL 焦虑!手把手带你用 NestJS + Prisma + DTO 写出“防弹”级后端代码
javascript·数据库·后端
倔强的石头_3 天前
KingbaseES 新版MySQL 兼容版体验:旧版迁移 + 功能实测
数据库
倔强的石头_6 天前
《Kingbase护城河》——数据库存储空间全景探测与精细化瘦身实战
数据库
冬奇Lab6 天前
每日一个开源项目(第134篇):Zvec - 阿里开源的嵌入式向量数据库,向量搜索界的 SQLite
数据库·人工智能·llm
ClouGence7 天前
Oracle CDC 架构优化:从主库直连到 DataGuard 备库同步
数据库·后端·oracle
无响应de神7 天前
三、用户与权限管理
数据库·mysql
麦聪聊数据7 天前
数据服务化时代:企业数据能力输出的核心路径
数据库