谓词下推(Predicate Pushdown)详解
一、什么是”谓词”
“谓词”(Predicate)借自逻辑学/数学,不是数据库发明的词。
在逻辑学里,一个命题由两部分组成:
- 主项(subject):被描述的对象 —— 比如”张三”
- 谓项(predicate):对主项的断言/判断 —— 比如”身高 > 180”
“张三身高 > 180”这个判断为真或假,这个”身高 > 180”就是谓词。
迁移到 SQL / 数据查询,谓词 = 一个能算出 TRUE/FALSE 的条件表达式:
SELECT * FROM t WHERE width > 2000 AND file_type = 'webp'
└────────┬────────┘ └──────┬──────┘
这是一个谓词 这也是一个谓词
(断言: width列>2000) (断言: file_type列=webp)WHERE 后面的每一处判断都是谓词。它”断言”某一行是否满足条件。
二、为什么叫”下推”
关键在**“下推”这个词的方向感**。想象系统的分层架构(上层是用户/计算引擎,下层是存储):
┌─────────────────────┐
│ 查询引擎 / 你的程序 │ ← 上层 (执行过滤的地方)
│ "读出所有行,再筛选" │
└─────────▲───────────┘
│ 默认做法: 数据先运上来, 上层再判断 TRUE/FALSE
┌─────────┴───────────┐
│ 存储层 (Parquet文件) │ ← 下层 (数据真正躺着的地方)
└─────────────────────┘
默认(不下推)
存储层只管”把数据搬上去”,过滤条件在上层执行。等于把 100 万行全读上来,再扔掉 99 万行 —— 浪费 I/O 和网络。
谓词下推(Predicate Pushdown)
把那个谓词(WHERE 条件)从上层”推下去”交给存储层,让存储层在读取时就判断、就地丢弃不满足的行 / 行组,只把满足的往上送。
┌─────────────────────┐
│ 查询引擎 │
│ 把 WHERE width>2000 │ ──┐
└─────────────────────┘ │ 把谓词"往下推"
▼ (push DOWN)
┌─────────────────────┐
│ 存储层 Parquet │ 在这里直接用谓词判断每个行组的 min/max
│ 整组跳过不满足的块 │ 只把命中的数据往上传
└─────────────────────┘
所以”下推” = 把判断逻辑顺着调用链往下游(靠近数据源头)推。越靠近源头过滤掉数据,后面要搬运 / 处理的数据就越少。
三、在 Parquet 里它具体怎么生效
Parquet 给每个行组(Row Group)的每一列都存了统计信息(min、max、null 计数)。谓词下推时:
谓词: WHERE width > 2000
行组0: width列 min=800, max=1500 → max < 2000, 整组都不可能满足 → 整组跳过(不解压!)
行组1: width列 min=1080, max=2400 → 可能有满足的 → 读取并逐行判断
行组2: width列 min=2200, max=3800 → min > 2000, 全部满足 → 整组保留
这就是为什么**“行组”和”谓词下推”是配套的**:行组越小、统计越细,谓词下推跳得越准。
例如 row_group_size=100 让每个 100 行的小块都有独立的 min/max,过滤时能精确跳过不相干的小块。
四、一句话总结
- 谓词 = 判断真假的过滤条件;
- 下推 = 把过滤条件从上层引擎推到下层存储去执行。
合起来”谓词下推” = 让存储层在数据源头就做过滤,少搬运无用数据。