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

相关推荐
Litluecat1 小时前
信创迁移:Oracle切换海量数据库,慢sql扫描
数据库·sql·oracle·信创·海量
消失在人海中2 小时前
Oracle的CURRENT REDO丢失,数据丢失风险分析
数据库·oracle
喵了几个咪2 小时前
选择第三方IAM还是自建权限体系?中小型后台系统权限架构决策指南
数据库·oracle·架构
Elastic 中国社区官方博客3 小时前
Kibana:使用 AI Chat 及 MCP 轻松创建 AI 原生仪表板
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·信息可视化
杨云龙UP4 小时前
Oracle Health Check巡检脚本使用SOP V2.0:从HTML原始报告→生成Word专业巡检报告→交付客户_2026-06-03
linux·运维·数据库·sql·oracle·报告·巡检
Database_Cool_4 小时前
Hudi 湖仓一体架构:阿里云 AnalyticDB MySQL 原生集成最佳实践
数据库·mysql·阿里云
我是一颗柠檬4 小时前
【Redis】发布订阅与消息队列Day8(2026年)
数据库·redis·后端·缓存
sukioe4 小时前
Redis 持久化+高可用详解:RDB/AOF/混合/主从/哨兵/集群
数据库·redis·缓存
全栈软件开发4 小时前
祈福导航系统V1.1更新 优化后端控制逻辑和前台UI
数据库·祈福导航系统