SELECT 是什么
ClickHouse 的 SELECT 是列式查询:只扫描用到的列,配合稀疏索引与分区裁剪,能在海量数据上做秒级聚合。语法与标准 SQL 接近,支持表达式、别名、WHERE、GROUP BY、HAVING、ORDER BY、LIMIT,并提供 LIMIT BY、WITH TOTALS、WINDOW 等扩展能力。大数据量下的典型用法是聚合与窗口计算,而不是逐行取明细。
基本信息
| 基本结构 | SELECT expr [[AS] alias] FROM table [WHERE] [GROUP BY] [HAVING] [ORDER BY] [LIMIT] |
|---|---|
| 常用聚合 | count()、sum、avg、min、max、uniq、quantile |
| 特色能力 | LIMIT BY 分组取前 N、WITH TOTALS、WINDOW 子句 |
| 裁剪依赖 | 分区键与排序键决定扫描范围 |
| 类型系统 | 强类型,聚合结果类型按输入推断 |
| 适用版本 | ClickHouse 22.x 及以上语法稳定 |
典型使用场景
- 按维度聚合统计,替代预计算的报表表
- 按时间范围做趋势分析,配合 toDate 与 toStartOfHour
- 明细抽样排查:加 LIMIT 快速看样本数据
- 去重计数:uniq 与 uniqExact 权衡精度与性能
- 分组取前 N 条:用 LIMIT BY 替代窗口函数
- 与外部 BI 对接:直接输出宽表结果集
示例
示例
SELECT
toDate(ts) AS d,
count() AS requests,
round(avg(value), 2) AS avg_value,
uniq(user_id) AS users
FROM metrics
WHERE ts >= now() - INTERVAL 7 DAY -- 范围条件才能裁剪分区与索引
GROUP BY d
HAVING requests > 100
ORDER BY d
LIMIT 100;常用聚合与函数
| 函数 | 作用 | 注意点 |
|---|---|---|
| count() | 行数统计 | 不带参数最快 |
| sum / avg | 求和与均值 | 数值类型才可用 |
| min / max | 取极值 | 常用于时间区间 |
| uniq | 近似去重计数 | 误差极小但非精确 |
| uniqExact | 精确去重计数 | 内存占用更高 |
| quantile | 分位数 | 如 quantile(0.95)(dur) |
查询与裁剪要点
- 对分区键用范围条件,避免函数包装导致无法裁剪
- 排序键前缀上的条件裁剪效果最好
- SELECT 只写需要的列,列式存储下能显著减少 IO
- PREWHERE 比 WHERE 更早过滤,适合高选择性的条件
- GROUP BY 后的 ORDER BY 要配合 LIMIT,避免无界排序
- 大结果集导出用 LIMIT 分批,避免一次性拉满内存
结果判读与常见偏差
- uniq 是近似去重,结果与 uniqExact 可能有极小差异
- avg 忽略 NULL,分母与业务口径要提前确认
- count() 与 countDistinct 的语义不同,别混用
- 时区影响 toDate 结果,跨时区业务要显式指定
- 浮点求和存在精度误差,金额类应存整数分
- HAVING 在分组后过滤,条件写成 WHERE 才能真正减少扫描量
ClickHouse 不允许在 SELECT 里使用未参与 GROUP BY 的裸列(除 any、argMax 等聚合包裹外),分组口径必须写清楚。
对分区键做函数包装(如 toYYYYMM(ts) 直接等于某个值)会失去裁剪能力,尽量改写成范围条件,把函数留在分区表达式里。
ORDER BY 没有 LIMIT 的大排序会消耗大量内存,超过内存限制才落盘,报表查询务必带 LIMIT 或先聚合降维。
读明细与读聚合要分开设计:明细查询靠 LIMIT 与主键条件,统计分析靠预聚合物化视图,混在一张表上很容易两头都不快。
查询性能实践
- 只选需要的列,列式存储按列读取,减少 IO 最直接
- 过滤条件尽量前置,放在 PREWHERE 或 WHERE 的靠前位置
- 大表统计先靠分区裁剪,再靠排序键裁剪
- GROUP BY 键基数过高时会占用大量内存,考虑预聚合
- 用 EXPLAIN 看读取的 granule 与行数,判断裁剪是否生效
- 结果集大时用 LIMIT 分批导出,避免一次性拉满内存
常见口径差异
- count() 统计行数,countDistinct 统计去重值,语义完全不同
- uniq 是近似去重,与 uniqExact 结果可能有极小差异
- avg 忽略 NULL,分母口径要与业务确认
- toDate 受时区影响,跨时区业务要显式指定
- 整型相除会截断,需要小数结果要转成浮点再算
- 浮点求和存在精度误差,金额类建议按分存整数
LIMIT 在聚合之后执行,不会减少扫描量;想真正减少读取,要靠分区与排序键裁剪,或用 PREWHERE 提前过滤。