当前位置: 首页 > news >正文

ClickHouse index_granularity 详解 - 若

ClickHouse index_granularity 详解

什么是 index_granularity

index_granularity 是ClickHouse中一个重要的性能配置参数,它定义了索引的粒度(granularity),即每多少个数据行会创建一个索引标记(index mark)。

基本概念

1. 索引标记的工作原理

数据行: [1, 2, 3, ..., 8192] [8193, 8194, ..., 16384] [16385, 16386, ...]
索引标记:      ↑标记1              ↑标记2              ↑标记3
  • 每8192行数据创建一个索引标记
  • 每个标记存储该段数据的边界信息(最小值、最大值等)
  • 查询时先通过索引标记快速定位到可能包含目标数据的段

2. 索引标记的创建依据

索引标记是根据ORDER BY子句中定义的列来创建的:

CREATE TABLE blocks (block_number UInt64,timestamp DateTime,block_hash String
) ENGINE = MergeTree()
ORDER BY (block_number, timestamp)  -- 索引标记基于这两列创建
SETTINGS index_granularity = 8192;

索引标记存储的信息:

  • 最小值:该段数据中ORDER BY列的最小值
  • 最大值:该段数据中ORDER BY列的最大值
  • 行数:该段包含的行数
  • 数据块位置:指向实际数据块的指针

3. 索引查找过程示例

-- 查询区块号范围
SELECT * FROM blocks WHERE block_number BETWEEN 10000 AND 20000;

索引查找过程:

  1. 检查索引标记1:max=8192 < 10000,跳过
  2. 检查索引标记2:min=8193, max=16384,与查询范围重叠,需要读取
  3. 检查索引标记3:min=16385 > 20000,跳过
  4. 只读取索引标记2对应的数据块

ORDER BY 对索引的影响

1. 不同ORDER BY的影响

单列排序:

ORDER BY block_number
-- 索引标记基于block_number创建
-- 适合按区块号查询

多列排序:

ORDER BY (block_number, timestamp)
-- 索引标记基于(block_number, timestamp)组合创建
-- 适合复合查询条件

你的表结构:

ORDER BY (start_block, end_block)
-- 索引标记基于(start_block, end_block)组合创建
-- 适合按区块范围查询

2. 索引效率的关键因素

ORDER BY列的选择:

-- 好的选择:经常用于WHERE条件的列
ORDER BY (block_number, timestamp)-- 不好的选择:很少用于查询的列
ORDER BY (random_column, unused_column)

列的顺序:

-- 好的顺序:选择性高的列在前
ORDER BY (block_number, timestamp)  -- block_number选择性高-- 不好的顺序:选择性低的列在前
ORDER BY (status, block_number)     -- status选择性低

查询模式匹配(左侧匹配原则):

-- 表结构
ORDER BY (start_block, end_block, status)-- 高效查询(遵循左侧匹配)
WHERE start_block = 12345                                    -- ✅ 使用第1列
WHERE start_block = 12345 AND end_block = 12350             -- ✅ 使用第1、2列
WHERE start_block = 12345 AND end_block = 12350 AND status = 'finished'  -- ✅ 使用第1、2、3列
WHERE start_block BETWEEN 1000 AND 2000                     -- ✅ 范围查询也遵循左侧匹配-- 低效查询(违反左侧匹配)
WHERE end_block = 12350                                     -- ❌ 跳过第1列,直接使用第2列
WHERE status = 'finished'                                   -- ❌ 跳过第1、2列,直接使用第3列
WHERE start_block = 12345 AND status = 'finished'          -- ❌ 使用第1、3列,跳过第2列

左侧匹配原则详解

1. 基本原理

ClickHouse的索引遵循左侧匹配原则,类似于MySQL的复合索引。查询时必须从左到右使用ORDER BY中的列,不能跳过中间的列。

2. 与MySQL的对比

-- MySQL复合索引:(start_block, end_block, status)
-- 遵循左侧匹配原则-- ClickHouse ORDER BY:(start_block, end_block, status)  
-- 同样遵循左侧匹配原则

3. 索引标记的工作原理

-- 表结构:ORDER BY (start_block, end_block, status)-- 索引标记存储:
标记1: start_block_min=1, start_block_max=1000, end_block_min=1, end_block_max=1000, status_min='init', status_max='pending'
标记2: start_block_min=1001, start_block_max=2000, end_block_min=1001, end_block_max=2000, status_min='init', status_max='pending'

4. 查询效率分析

-- ✅ 高效查询:WHERE start_block = 1500
-- 可以快速定位到标记2,因为start_block在范围内-- ❌ 低效查询:WHERE end_block = 1500  
-- 无法有效使用索引,因为索引标记的end_block范围重叠
-- 需要扫描多个标记或全表扫描

5. 实际应用示例

-- 你的block_tasks表
CREATE TABLE block_tasks (start_block UInt64,end_block UInt64,status String
) ENGINE = MergeTree()
ORDER BY (start_block, end_block)
SETTINGS index_granularity = 8192;-- 高效查询模式
SELECT * FROM block_tasks WHERE start_block = 12345;                    -- ✅ 使用第1列
SELECT * FROM block_tasks WHERE start_block BETWEEN 1000 AND 2000;      -- ✅ 范围查询第1列
SELECT * FROM block_tasks WHERE start_block = 12345 AND end_block = 12350; -- ✅ 使用第1、2列-- 低效查询模式  
SELECT * FROM block_tasks WHERE end_block = 12350;                      -- ❌ 跳过第1列
SELECT * FROM block_tasks WHERE status = 'finished';                    -- ❌ 跳过第1、2列

6. 最佳实践

-- 设计ORDER BY时要考虑查询模式
-- 将最常用于WHERE条件的列放在前面
ORDER BY (最常用的列, 次常用的列, 较少用的列)-- 例如:如果经常按start_block查询,偶尔按start_block+end_block查询
ORDER BY (start_block, end_block)  -- 好的设计-- 如果经常按end_block查询,偶尔按start_block查询  
ORDER BY (end_block, start_block)  -- 更好的设计

默认值和推荐值

  • 默认值:8192
  • 推荐值:8192(ClickHouse官方推荐)
  • 常见值:1024, 4096, 8192, 16384, 65536

不同值的影响

index_granularity 内存使用 查询速度 写入速度 适用场景
1024 (小) 频繁更新,查询较少
4096 (中小) 较高 较慢 较快 平衡型应用
8192 (默认) 中等 中等 通用场景,推荐
16384 (大) 较低 中等 较慢 批量写入,查询频繁
65536 (很大) 中等 大批量数据,查询较少

为什么选择 8192

1. 数学优势

  • 2的幂次方:8192 = 2^13,便于计算机处理
  • 内存对齐:与系统页面大小(8KB)匹配
  • 缓存友好:适合CPU缓存行大小

2. 性能平衡

  • 查询性能:能够快速定位到相关数据段
  • 内存使用:不会创建过多索引标记
  • 写入性能:在批量插入时保持良好的性能

3. 经验优化

  • ClickHouse团队经过大量测试得出的最佳值
  • 适用于大多数应用场景
  • 平衡了各种性能指标

实际应用示例

1. 区块数据表

CREATE TABLE blocks (block_number UInt64,timestamp DateTime,block_hash String
) ENGINE = MergeTree()
ORDER BY (block_number, timestamp)
SETTINGS index_granularity = 8192;

查询示例:

-- 查询特定区块范围
SELECT * FROM blocks WHERE block_number BETWEEN 1000000 AND 1001000;

性能分析:

  • 100万行数据,8192粒度,约122个索引标记
  • 查询时只需检查122个标记,然后读取对应的8192行数据段
  • 相比全表扫描,性能提升显著

2. 交易数据表

CREATE TABLE transactions (tx_hash String,block_number UInt64,from_address String,to_address String
) ENGINE = MergeTree()
ORDER BY (block_number, tx_hash)
SETTINGS index_granularity = 8192;

3. 日志数据表

CREATE TABLE logs (block_number UInt64,log_index UInt32,address String,data String
) ENGINE = MergeTree()
ORDER BY (block_number, log_index)
SETTINGS index_granularity = 8192;

如何选择合适的值

1. 数据量考虑

-- 小表(<10万行):使用较小值
SETTINGS index_granularity = 1024;-- 中等表(10万-1000万行):使用默认值
SETTINGS index_granularity = 8192;-- 大表(>1000万行):使用较大值
SETTINGS index_granularity = 16384;

2. 查询模式考虑

-- 频繁点查询:使用较小值
SETTINGS index_granularity = 4096;-- 范围查询为主:使用默认值
SETTINGS index_granularity = 8192;-- 批量查询:使用较大值
SETTINGS index_granularity = 16384;

3. 写入模式考虑

-- 频繁小批量写入:使用较小值
SETTINGS index_granularity = 4096;-- 批量写入:使用默认值
SETTINGS index_granularity = 8192;-- 大批量写入:使用较大值
SETTINGS index_granularity = 32768;

性能测试示例

1. 查询性能对比

-- 测试不同粒度的查询性能
-- 数据量:1000万行
-- 查询:WHERE block_number BETWEEN 1000000 AND 1001000-- index_granularity = 1024
-- 索引标记数:约9766个
-- 查询时间:较慢-- index_granularity = 8192  
-- 索引标记数:约1221个
-- 查询时间:快-- index_granularity = 65536
-- 索引标记数:约153个  
-- 查询时间:中等

2. 内存使用对比

-- 索引标记内存使用估算
-- 每个标记约占用几十字节-- 1000万行数据:
-- 1024粒度:约9766个标记,内存使用高
-- 8192粒度:约1221个标记,内存使用中等
-- 65536粒度:约153个标记,内存使用低

最佳实践

1. 默认配置

-- 大多数情况下使用默认值
SETTINGS index_granularity = 8192;

2. 特殊场景调整

-- 高并发点查询
SETTINGS index_granularity = 4096;-- 大批量数据写入
SETTINGS index_granularity = 16384;-- 内存受限环境
SETTINGS index_granularity = 32768;

3. 监控和调优

-- 查看表设置
SELECT * FROM system.tables WHERE name = 'your_table';-- 监控查询性能
SELECT * FROM system.query_log 
WHERE query LIKE '%your_table%' 
ORDER BY event_time DESC;

常见问题

Q: 如何确定最佳值?

A: 通过性能测试,监控查询时间和内存使用,选择平衡点。

Q: 可以动态调整吗?

A: 不可以,需要在创建表时设置,修改需要重建表。

Q: 不同表可以使用不同值吗?

A: 可以,每个表可以独立设置。

Q: 设置过小会有什么问题?

A: 内存使用增加,写入性能下降。

Q: 设置过大会有什么问题?

A: 查询时需要扫描更多数据,查询性能下降。

总结

index_granularity = 8192 是ClickHouse的推荐配置,它在查询性能、内存使用和写入性能之间达到了很好的平衡。

关键要点

  1. 索引基于ORDER BY列:索引标记是根据ORDER BY子句中定义的列创建的
  2. 左侧匹配原则:查询时必须从左到右使用ORDER BY中的列,不能跳过中间的列
  3. 查询匹配很重要:WHERE条件应该尽量使用ORDER BY列,以获得最佳性能
  4. 列顺序影响效率:选择性高的列应该放在ORDER BY的前面
  5. 默认值适用大多数场景:8192是经过优化验证的最佳选择

Web3数据收集项目应用

  1. 区块数据:使用默认值8192,适合范围查询
  2. 交易数据:使用默认值8192,平衡查询和写入
  3. 日志数据:使用默认值8192,适合批量查询
  4. 任务管理:ORDER BY (start_block, end_block) 配合8192粒度,高效支持任务查询

除非有特殊需求,否则建议使用默认值8192,这是经过优化验证的最佳选择。

http://www.hskmm.com/?act=detail&tid=15632

相关文章:

  • P13885 [蓝桥杯 2023 省 Java/Python A] 反异或 01 串
  • clickhouse轻量级更新 - 若
  • 西电PCB设计指南第3章学习笔记
  • Vitrualbox、kali、metaspolitable2下载安装
  • LazyLLM端到端实战:用RAG+Agent实现自动出题与学习计划的个性化学习助手智能体
  • 补充图
  • 【阿里云事件总线】域名+邮件推送+事件总线=实现每天定时邮件!
  • llm入门环境
  • FLASH空间划分/存储数据至指定CODEFLASH位置
  • SOOMAL 降噪数据表
  • 案例分享|借助IronPDF IronOCR,打造医疗等行业的智能化解决方案
  • ClickHouse UPDATE 操作问题解决方案 - 若
  • 利用 Milvus + RustFS,快速打造一个 RAG!
  • Docker 私有镜像仓库 Harbor 安装部署带签名认证
  • ARC180 做题记
  • 借助Aspose.HTML控件,使用 Python 编辑 HTML
  • 微前端 micro-app 在vue 中的路由跳转问题
  • 1. 设计模式--工厂办法模式
  • 汽车视频总线采集过程中,如何兼顾响应速度和可靠性?
  • P8865 [NOIP2022] 种花
  • traefik 反向代理 + IdentityServer4
  • 麦角硫因制备关键技术和设备
  • 2025年十大好用网盘推荐:功能、口碑与性价比大对比
  • 卡特兰数
  • Word-通过宏格式化文档中的表格和图片
  • 反向代理 traefik - 健康检查
  • 一些想法 - CelestialZ
  • 使用 Ansible 批量安装 Docker
  • 编程规范---日志规范
  • 深入解析:find_code 插件 react_vite