谓词下推(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,过滤时能精确跳过不相干的小块。


四、一句话总结

  • 谓词 = 判断真假的过滤条件;
  • 下推 = 把过滤条件从上层引擎推到下层存储去执行。

合起来”谓词下推” = 让存储层在数据源头就做过滤,少搬运无用数据