自适应查询执行的核心架构:反馈循环、决策点、执行模型
自适应查询执行不是某个单一技术,而是一套完整的架构设计。说白了,它就像给数据库装了一套「自动驾驶系统」——能感知路况、判断风险、实时调整路线。这套架构由三个核心部件组成:反馈循环、决策点和执行模型。缺一个,自适应就玩不转。
我在项目中见过不少团队,一上来就写自适应策略代码,结果跑起来要么反馈太慢,要么决策点选错位置,要么执行模型根本接不住调整。嗯,今天我们就把这套架构拆开来看。
1. 反馈循环:自适应的心脏
反馈循环是自适应查询执行的基础。没有反馈,你拿什么做决策?
我个人习惯把反馈循环分成四个阶段:
- 监控(Monitor):采集运行时数据,比如算子处理行数、内存消耗、执行时间
- 分析(Analyze):对比预期值和实际值,判断是否出现偏差
- 决策(Decide):根据偏差类型和程度,选择调整策略
- 行动(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. 三者的协作关系
反馈循环、决策点、执行模型不是各自为政的。它们必须紧密配合。我画了一张图,帮你理解它们的关系:
这张图展示了完整的协作流程:
- 反馈循环持续监控执行状态,发现偏差后触发分析
- 分析结果送到决策点,决策点判断「现在是不是调整的好时机」
- 决策通过后,执行模型负责把调整落地——可能是切换算子、调整并行度、或者重新分配内存
- 调整完成后,反馈循环继续监控,验证调整是否有效
实战经验:我在做TPC-H测试时发现,反馈循环的「分析」阶段最容易成为瓶颈。后来我把分析逻辑拆成两级:第一级做快速偏差检测(只看行数),第二级做详细分析(看内存和CPU)。80%的情况第一级就能搞定,只有20%需要走完整分析路径。
5. 架构设计的三个坑
最后,我总结一下自适应查询执行架构设计中常见的三个坑:
- 坑一:反馈循环和决策点耦合太紧。我曾经把决策逻辑直接写在反馈循环里,结果想换决策策略时,整个反馈循环都得重写。正确的做法是让反馈循环只负责数据采集,决策逻辑独立出去。
- 坑二:执行模型不支持回滚。调整失败了怎么办?如果执行模型没有回滚机制,整个查询可能崩溃。我建议至少保留一个「安全执行计划」作为兜底。
- 坑三:忽略元数据更新。自适应调整后,统计信息、基数估计这些元数据也要同步更新。否则下一次决策还是基于旧数据,等于白调。