ORDER BY 决定什么
ORDER BY 决定数据在 part 内的物理顺序,并据此生成稀疏主索引:默认每 index_granularity 行记一条索引,查询条件命中排序键前缀时可以按粒度直接跳过数据块。它和分区一起构成 ClickHouse 查询性能最关键的两项表设计。
基本信息
| 排序键选择 | 把高频过滤、基数可控的列放前面,时间列通常参与排序 |
|---|---|
| 主键声明 | PRIMARY KEY 必须是 ORDER BY 的前缀,不写时等于 ORDER BY |
| 不保证的事 | MergeTree 不保证行唯一,有序性也只在单个 part 内成立 |
| 裁剪条件 | WHERE 命中排序键前缀才能跳过数据块 |
| 验证方式 | 用 EXPLAIN 或 system.query_log 里的 read_rows 对比扫描量 |
典型使用场景
- 按查询模式设计排序键:先高频等值过滤,再时间范围
- 用 read_rows 验证裁剪是否真的生效
- 排查慢查询:确认过滤条件是否命中排序键前缀
- 评估是否需要拆出物化视图服务另一类查询
- 复核排序键长度:字段过多会让索引本身变重
设计与验证
设计与验证
-- 命中排序键前缀的过滤可以被裁剪
SELECT count() FROM metrics WHERE metric = 'cpu' AND ts >= now() - INTERVAL 1 HOUR;
-- 不命中前缀的过滤只能扫描全部数据
SELECT count() FROM metrics WHERE value > 100;
-- 查看表的排序键与主键定义
SELECT name, sorting_key, primary_key FROM system.tables WHERE table = 'metrics';
-- 用执行计划确认是否发生了裁剪
EXPLAIN indexes = 1 SELECT count() FROM metrics WHERE metric = 'cpu';排序键设计与后果
| 设计 | 效果 | 风险 |
|---|---|---|
| 高频过滤列放前面 | 可裁剪的范围最大 | 后续列只有在前缀命中后才起作用 |
| 高基数列放前面 | 索引条目增多 | 索引体积可能接近数据量,裁剪收益反降 |
| 时间列放尾部 | 时间范围过滤仍可裁剪 | 单独按时间查询时收益有限 |
| 排序键字段过多 | 覆盖更多查询模式 | 索引更大、写入成本更高 |
用 EXPLAIN 或 system.query_log 里的 read_rows 对比实际扫描行数,是验证排序键是否生效最直接的办法;只看查询耗时会受缓存与并发影响。
主键必须是排序键的前缀,两者不一致时建表会直接报错;实际运维中改变排序键通常意味着新建表或物化视图再迁移数据,因为已有 part 已经按旧顺序落盘。