PREWHERE 是怎么省 IO 的
PREWHERE 先只读取过滤条件涉及的列,把不满足条件的行筛掉之后,再读查询需要的其他列。宽表上这个顺序很关键:过滤列往往只占少数几列,先筛掉大部分数据再读宽列,磁盘读取量能下降一个量级。
自动搬运与手动指定
优化器默认会尝试把适合的条件从 WHERE 搬到 PREWHERE(由 optimize_move_to_prewhere 控制),但自动判断有边界,复杂查询可以显式写出 PREWHERE 固定执行顺序。限制是它只能引用表中的列,不能引用 SELECT 里定义的别名或聚合结果,写了会直接报错。
基本信息
| 写法 | SELECT ... PREWHERE 条件 WHERE 条件 |
|---|---|
| 优化开关 | optimize_move_to_prewhere 默认开启 |
| 硬限制 | 只能引用表列,不能引用 SELECT 别名或聚合结果 |
| 验证方式 | EXPLAIN 查看过滤是否前置 |
| 适用版本 | ClickHouse 各主流版本均支持 |
示例
示例
SELECT event_time, user_id, props
FROM events
PREWHERE event_type = 'click' AND event_date = today()
WHERE user_id > 0
LIMIT 100;
-- 看执行计划,确认过滤是否前置
EXPLAIN indexes = 1
SELECT count() FROM events PREWHERE event_type = 'click';
-- 对照实验:关闭自动搬运,比较两次读取行数
SET optimize_move_to_prewhere = 0;典型使用场景
- 大宽表过滤:先用高选择性条件把数据缩小,再读宽列
- 慢查询优化:把最省列、过滤效果最好的条件前置
- 验证效果:用 EXPLAIN 确认过滤发生在读取阶段
- 对照实验:切换自动搬运开关,比较读取行数与耗时
- 配合分区裁剪:把分区键相关条件放进 PREWHERE 一起生效
写法与读取行为
| 写法 | 读取行为 | 效果 |
|---|---|---|
| 高选择性条件放 PREWHERE | 只读该条件的列并先行筛行 | IO 下降最明显 |
| 低效条件留在 WHERE | 读取较宽数据后再过滤 | 开销较大 |
| PREWHERE 引用别名 | 直接报错 | 语句需要重写 |
| 自动搬运开启 | 优化器自行选择条件 | 多数场景可用,复杂查询建议显式写 |
决定收益的是条件本身的选择性:先裁掉大比例数据再读宽列,IO 才会明显下降;条件写在哪个关键字里只是手段,不是目的。
把低效条件放进 PREWHERE、把高选择性条件留在 WHERE,反而会增加读取量;显式使用时要按选择性排布,而不是按书写习惯。