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 一站式从入门到实战》系列文章。

相关推荐
星蓝_starblue2 小时前
零服务器、零数据库!开源growth-board,利用GitHub自动管理刷题/学习/求职全流程
服务器·数据库·程序人生·系统架构·node.js·github·改行学it
三8444 小时前
sqli-labs1-10通关笔记
数据库
BullSmall6 小时前
Another Redis Desktop Manager(ARDM)下载指南
数据库·redis·缓存
SelectDB6 小时前
Apache Doris 倒排索引工作原理:全文检索提速 59 倍,点查提速 14 倍
数据库
码农颜6 小时前
5.1.1 原⼦性
数据库·mysql
机建狂魔6 小时前
Codex 接入第三方模型 API 实战:以 Mimo 为例
java·服务器·数据库·ai·ai编程·codex
山峰哥7 小时前
数据库工程与SQL调优:从慢查询到秒级响应的实战之路
java·开发语言·数据库·sql·深度优先·启发式算法
moMo7 小时前
# 向量数据库入门:从"关键词匹配"到"语义理解"
数据库
ACP广源盛139246256737 小时前
DeepSeek‑V4‑Flash 公测@ACP#昇腾 950 国产算力组合落地,国产 PCIe 交换芯片 IX9104 有哪些硬件机会
大数据·数据库·人工智能·分布式·单片机·嵌入式硬件·microsoft
进击的女IT8 小时前
通过mysql中的Data目录恢复数据库数据
数据库·mysql