首页/数据/NoSQL 速查/ORDER BY 排序键与稀疏索引
ClickHouse56 条

ORDER BY 排序键与稀疏索引

ORDER BY 决定数据在 part 内的物理顺序,并生成每 index_granularity 行一条的稀疏主索引;WHERE 命中排序键前缀时可按粒度跳过数据块。

NoSQL 速查数据更新于 2026-10-13

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 已经按旧顺序落盘。

易错点

  • 把 UUID 这类高基数列放在排序键最前面,索引条目会急剧膨胀,裁剪收益也跟着变差
  • WHERE 条件里对排序键使用函数(如 toString(ts))会导致无法裁剪,函数应写在值一侧或改用等价的可裁剪条件
  • 以为排序键能保证唯一:它只决定顺序与索引,重复行照样存在,去重要靠合适的表引擎
  • 只在很小数据集上验证排序键效果,数据量与分区规模变化后裁剪收益可能完全不同,应结合 read_rows 在真实规模下验证

数据来源与参考

  • ClickHouse Docs: MergeTree primary key and indexes
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌