自适应查询执行的核心架构:反馈循环、决策点、执行模型

自适应查询执行不是某个单一技术,而是一套完整的架构设计。说白了,它就像给数据库装了一套「自动驾驶系统」——能感知路况、判断风险、实时调整路线。这套架构由三个核心部件组成:反馈循环、决策点和执行模型。缺一个,自适应就玩不转。

我在项目中见过不少团队,一上来就写自适应策略代码,结果跑起来要么反馈太慢,要么决策点选错位置,要么执行模型根本接不住调整。嗯,今天我们就把这套架构拆开来看。

1. 反馈循环:自适应的心脏

反馈循环是自适应查询执行的基础。没有反馈,你拿什么做决策?

我个人习惯把反馈循环分成四个阶段:

  1. 监控(Monitor):采集运行时数据,比如算子处理行数、内存消耗、执行时间
  2. 分析(Analyze):对比预期值和实际值,判断是否出现偏差
  3. 决策(Decide):根据偏差类型和程度,选择调整策略
  4. 行动(Act):执行调整,比如切换Join算法、重新分配内存

你想想看,这其实和操作系统的调度循环很像。但数据库的反馈循环有个特殊要求——延迟必须可控。我曾经遇到一个案例,反馈循环每轮要跑200ms,结果查询都执行完了,调整还没生效。这就是典型的「反馈滞后」问题。

核心指标:反馈延迟

反馈循环的周期必须小于查询执行时间的10%。否则自适应策略不仅没帮助,反而成了负担。

那么反馈循环应该采集哪些数据?我列个表,方便你对照:

数据类别 具体指标 采集频率 用途
算子级 输出行数、CPU时间、内存使用 每批次 判断基数估计偏差
流水线级 缓冲区占用、生产者/消费者速率 每100ms 检测数据倾斜或阻塞
全局级 总执行时间、I/O等待、锁竞争 每算子完成 评估整体健康度

避坑指南:我曾经在项目里把所有指标都设成每行采集一次,结果反馈循环本身消耗了30%的CPU。后来改成「按需采样+自适应频率」,才把开销压到5%以下。

2. 决策点:在哪里踩刹车

决策点就是查询执行过程中「停下来想一想」的位置。选错决策点,要么调整太晚,要么调整太频繁。

常见的决策点有三种:

  • 算子边界:每个算子执行完毕后检查。优点是粒度细,缺点是开销大。
  • 流水线边界:整条流水线切换时检查。比如从扫描阶段进入Join阶段。我比较推荐这种,平衡了灵活性和开销。
  • 阻塞点:在需要物化中间结果的节点检查。比如Sort、Hash Join的构建阶段结束。

为什么会这样?因为不同的决策点对应不同的「后悔成本」。举个例子:

-- 假设你选了Hash Join,但实际数据量远超预期
-- 如果在构建阶段结束时就检查,你还能切换到Sort Merge Join
-- 如果等到Hash Join全部跑完,数据已经物化,切换成本就高了

我个人习惯在流水线边界和阻塞点各设一个决策点。前者做轻量调整(比如调整并行度),后者做重量调整(比如切换Join算法)。

注意:不要在每行数据到达时都做决策。我见过有人这么干,结果决策逻辑的执行时间比查询本身还长。决策点的频率要和调整的「重量」匹配。

3. 执行模型:自适应如何落地

执行模型决定了「调整后的计划怎么跑」。传统数据库的执行模型是静态的——计划生成后就不变了。自适应执行模型必须支持运行时计划修改。

目前主流的有三种执行模型:

模型 特点 适用场景 代表系统
火山模型 + 状态迁移 算子内部维护状态,支持动态切换 OLTP、短查询 PostgreSQL 自适应Join
Morsel-Driven 数据分片,每个分片独立调度 OLAP、长查询 HyPer、DuckDB
向量化 + 动态编译 运行时生成新代码,替换旧算子 分析型、复杂查询 ClickHouse、Spark

你想想看,火山模型虽然灵活,但状态迁移的开销不小。Morsel-Driven模型天生适合自适应——每个Morsel可以独立调整执行策略。我在做OLAP引擎时,就选了Morsel-Driven模型,因为它的「隔离性」太好了:一个分片调整失败,不影响其他分片。

关键设计原则:执行模型必须支持「无损切换」。也就是说,调整前后不能丢数据,也不能重复处理数据。这通常靠物化点和版本号来实现。

4. 三者的协作关系

反馈循环、决策点、执行模型不是各自为政的。它们必须紧密配合。我画了一张图,帮你理解它们的关系:

自适应查询执行核心架构协作图 反馈循环 监控 (Monitor) 分析 (Analyze) 决策 (Decide) 行动 (Act) 决策点 算子边界 流水线边界 阻塞点 用户定义点 执行模型 火山模型 Morsel 向量化 动态编译 反馈循环采集数据 → 决策点判断时机 → 执行模型落地调整 → 反馈循环继续监控

这张图展示了完整的协作流程:

  1. 反馈循环持续监控执行状态,发现偏差后触发分析
  2. 分析结果送到决策点,决策点判断「现在是不是调整的好时机」
  3. 决策通过后,执行模型负责把调整落地——可能是切换算子、调整并行度、或者重新分配内存
  4. 调整完成后,反馈循环继续监控,验证调整是否有效

实战经验:我在做TPC-H测试时发现,反馈循环的「分析」阶段最容易成为瓶颈。后来我把分析逻辑拆成两级:第一级做快速偏差检测(只看行数),第二级做详细分析(看内存和CPU)。80%的情况第一级就能搞定,只有20%需要走完整分析路径。

5. 架构设计的三个坑

最后,我总结一下自适应查询执行架构设计中常见的三个坑:

  • 坑一:反馈循环和决策点耦合太紧。我曾经把决策逻辑直接写在反馈循环里,结果想换决策策略时,整个反馈循环都得重写。正确的做法是让反馈循环只负责数据采集,决策逻辑独立出去。
  • 坑二:执行模型不支持回滚。调整失败了怎么办?如果执行模型没有回滚机制,整个查询可能崩溃。我建议至少保留一个「安全执行计划」作为兜底。
  • 坑三:忽略元数据更新。自适应调整后,统计信息、基数估计这些元数据也要同步更新。否则下一次决策还是基于旧数据,等于白调。