ClickHouse MergeTree主键索引与稀疏索引机制
ClickHouse MergeTree的主键索引是稀疏索引(Sparse Index),请解释其工作原理:index_granularity和index_granularity_bytes如何控制索引粒度?主键索引如何加速查询(Granule裁剪机制)?与MySQL B+Tree索引有什么本质区别?给出一个ORDER BY设计的优化案例。
回答
我还是少年
ClickHouse稀疏索引工作原理:
1. 索引结构:
- 不是B+Tree,而是排序后的列值数组
- 每
index_granularity(默认8192)行记录一个索引条目 - 索引文件
primary.idx存储在列数据之外的元数据层
2. 索引粒度参数:
-- 表级设置
ENGINE = MergeTree
ORDER BY (CounterID, EventDate)
SETTINGS index_granularity = 8192, index_granularity_bytes = 10485760;
index_granularity:每N行生成一个索引标记(固定行数)index_granularity_bytes:每M字节生成一个索引标记(自适应,10MB)- 两者取最先达到的阈值
3. Granule裁剪机制:
SELECT * FROM table WHERE CounterID = 12345;
- 二分查找primary.idx,定位到包含CounterID=12345的Granule范围
- 只读取可能包含目标数据的Granule(跳过无关Granule)
- 每个Granule内部顺序扫描(少量数据)
4. 与MySQL B+Tree的本质区别:
| 维度 | ClickHouse稀疏索引 | MySQL B+Tree |
|---|---|---|
| 存储位置 | 与数据分离 | 与数据集成(聚簇索引) |
| 粒度 | 每8192行一个标记 | 每行一个键值 |
| 查找方式 | 二分查找Granule | B+Tree多级查找 |
| 适用 | 范围扫描/批量聚合 | 点查/小范围查 |
| 内存占用 | 极小(1个Granule≈1个int) | 较大(维护完整树结构) |
5. ORDER BY优化案例:
-- ❌ 错误:先排低基数,后排高基数
ORDER BY (status, user_id) -- status=1的值散落在各处
-- ✅ 优化:先排高基数或查询频繁的列
ORDER BY (user_id, event_time) -- 同一用户数据连续存储