13-数据库学习笔记(查询执行处理模型)

一.查询执行基础概述

1.为什么SQL不能直接执行?

因为SQL是一种声明式语言,只告诉数据库我要什么结果,但不说"具体怎么找",数据库是无法执行查询操作的,所以我们必须把SQL翻译成一张施工图纸 ,这张图纸就叫做查询计划。

整个查询步骤可以理解成:用户写一条 SQL → 数据库分析它 → 制定执行方案 → 调用底层算子处理数据 → 返回结果

  • 查询计划长得像一棵倒过来的树(DAG 有向无环图),由一个个算子(封装了特定数据操作逻辑的数据处理执行单元)搭成。
  • 数据从叶子往上流,最顶上(根节点)输出的就是最终看到的结果。

2.流水线与阻断算子

流水线: 数据像流水一样,从一个算子流向下一个算子。来一行、处理一行、输出一行,不用攒数据,边输入边输出。

阻断算子: 必须等前面所有数据处理完,才能继续向后输出结果。

这样设计的好处与代价:

优点 树状结构分工清楚、算子能复用;流水线让数据"流着走",不用反复落地存盘,又快又省

缺点 一旦碰上排序、连接这类"阻断算子",流水线就得暂停、还得腾出内存先存数据,整体就慢下来了

二.数据库查询处理模型

查询处理模型 = 数据库执行 SQL 时,各个算子之间如何传递数据、如何调用、如何运行。

1.为什么需要"处理模型"?

不同业务场景数据库负载差异极大,OLTP事务型业务追求低延迟、少量数据读写;OLAP分析型业务适配海量数据批量扫描。对于不同的场景,数据库实际干的活完全不一样,没法用单一模式适配这两种场景,于是业界迭代出了三类主流处理模型(迭代器 / 物化 / 向量化)

2.处理模型核心逻辑(控制流+数据流)

处理模型定义了查询计划的执行方式与数据流动机制,其核心由控制流和数据流两部分构成:控制流 决定算子的调用与调度方式,数据流决定数据在算子间的传递模式。

3.迭代器模型(火山模型)

迭代器模型是关系数据库查询执行引擎的经典架构 ,因为数据从底层逐层向上流动、像火山喷发一样,也被称为火山模型 。它的核心设计思想是:所有算子都实现一套统一的迭代器接口,上层算子只需要调用下层算子的接口,就能逐行获取处理好的数据。整个执行计划就是一棵由迭代器算子组成的树,通过递归调用完成数据处理。所有算子统一实现Open()、Next()、Close()三类函数,Open()初始化算子资源,Next()单次调取一条元组,无数据时返回结束标记,Close()释放资源

(1)执行流程

  1. 初始化阶段 :从根节点开始,递归调用 open(),一直传递到最底层的扫描算子,完成所有资源初始化。
  2. 数据处理阶段 :从根节点开始调用 next(),递归向下层要数据;下层处理完一行,就向上层返回一行;层层处理,最终从根节点输出一行结果。
  3. 结束阶段 :当 next() 返回空时,从根节点递归调用 close(),逐层释放资源。

(2)例子

示例SQL:

sql 复制代码
SELECT name, salary 
FROM employees 
WHERE department = '研发部' 
ORDER BY salary DESC;

执行计划树:

plaintext 复制代码
Sort(salary DESC)       ------ 阻断算子(根节点)
  └── Project(name, salary)  ------ 流水线算子
        └── Select(department='研发部')  ------ 流水线算子
              └── SeqScan(employees)    ------ 流水线算子(叶子节点)

原始数据:

id name department salary
1 张三 研发部 18000
2 李四 市场部 15000
3 王五 研发部 22000
4 赵六 研发部 16000
5 孙七 人事部 13000
6 周八 研发部 20000
执行全过程
1. 初始化:递归 open()

从根节点自上而下调用 open(),逐层初始化资源,最终扫描算子打开表文件,行指针指向第 1 行。

2. 核心迭代:递归 next()

客户端反复调用根节点 next(),每次获取一行结果。

  • 第 1 次调用 Sort.next()(阻断逻辑触发)
    Sort 是阻断算子,必须先拿到全部数据才能输出。它循环调用下层拉取数据:
    1. 数据沿 SeqScan → Select → Project 逐行流动:扫描读一行 → 过滤部门 → 提取目标列,符合条件的行存入 Sort 内部缓冲区。
    2. 扫描到文件末尾返回 null 后,Sort 对 4 条有效数据执行全量降序排序:
      王五(22000) → 周八(20000) → 张三(18000) → 赵六(16000)
    3. 返回排序后的第 1 行给客户端。
  • 第 2~4 次调用 Sort.next()
    直接从内部缓冲区按指针逐行返回,无需再访问下层算子。
  • 第 5 次调用 Sort.next()
    缓冲区读取完毕,返回 null,查询结束。
3. 收尾:递归 close()

从根节点自上而下调用 close(),逐层释放资源、关闭文件。

(3)优缺点

优点:1.简单统一(所有算子接口统一) 2.支持流水线 3.节省内存,不用保存大量中间结果

缺点:频繁进行函数调用。开销大

4.物化模型

(1)执行流程

物化模型:每个算子把自己的全部结果计算出来,保存下来,再交给下一个算子。也就是:先算完 → 存起来 → 再传给下一层 。对比迭代器的「逐行流动」,物化模型的核心是:每个算子一次性处理完所有输入,生成完整的中间结果集,再整体交付给上层算子。每层都是 "全量输入→全量输出",没有逐行迭代的过程。

(2)例子

第 1 步:全表扫描 → 物化全量原始数据

SeqScan 算子一次性读取 employees 表全部 6 行数据,生成中间结果集 R1(完整原始表),整体交付给 Select 算子。

中间结果 R1:全部 6 行原始数据

第 2 步:选择过滤 → 物化过滤后结果

Select 算子拿到完整的 R1 后,一次性对所有 6 行执行条件过滤,保留研发部的 4 行,生成中间结果集 R2,整体交付给 Project 算子。

中间结果 R2:4 行符合条件的完整数据(张三、王五、赵六、周八)

第 3 步:列投影 → 物化简选列结果

Project 算子拿到完整的 R2 后,一次性去掉多余列,只保留 name 和 salary 两列,生成中间结果集 R3,整体交付给 Sort 算子。

中间结果 R3:4 行两列数据 ------ (张三,18000)、(王五,22000)、(赵六,16000)、(周八,20000)

第 4 步:排序 → 物化最终结果

Sort 算子拿到完整的 R3 后,一次性执行全量降序排序,生成最终结果集 R4,整体返回给客户端。

最终结果 R4:王五 (22000) → 周八 (20000) → 张三 (18000) → 赵六 (16000)

(3)核心特点

  1. 全量交付,无逐行交互
    算子之间只有一次完整结果的交付,没有反复的 next() 调用,不存在行级流水线。
  2. 所有算子本质都是阻断式
    每个算子都必须等下层全部做完才能开始工作,没有流水线算子与阻断算子的区分。
  3. 内存占用高
    每一层都会产生一份完整的物化中间结果,n 层算子就会有 n 份全量数据副本,数据量大时内存压力极大。
  4. 结果返回延迟高
    必须等从最底层到最顶层所有算子全部执行完,才能返回结果,无法像迭代器模型那样边执行边输出第一行。

5.向量化模型

(1)执行流程

向量化执行 = 不再一次处理一条记录,而是一次处理一批记录,利用CPU高速计算能力提升查询性能。

(2)例子

1. 初始化:递归 open()

与迭代器模型逻辑一致,自上而下递归初始化,每层算子分配批次缓冲区,用于存放一批列向量数据。

2. 核心迭代:逐批拉取与处理

客户端反复调用根节点next(),每次获取一批结果,而非单行。

第 1 次调用 Sort.next()(批级阻断触发)

Sort 是阻断算子,需收集全部批次数据后排序输出。它循环调用下层算子拉取批次:

  • 第 1 批数据流转
    SeqScan.next () → 读取前 2 行,返回列向量批次:name=[张三, 李四],department=[研发部, 市场部],salary=[18000, 15000]
    Select.next () → 批量判断 department 向量,一次性过滤掉不符合条件的李四,输出过滤后批次:name=[张三],department=[研发部],salary=[18000]
    Project.next () → 批量剔除 department 列,输出投影后批次:name=[张三],salary=[18000]
    Sort 将该批数据存入全局缓冲区。
  • 第 2 批数据流转
    SeqScan.next () → 读取中间 2 行,返回批次:name=[王五, 赵六],department=[研发部, 研发部],salary=[22000, 16000]
    Select 批量过滤(全部符合)→ Project 批量投影 → Sort 存入缓冲区。
  • 第 3 批数据流转
    SeqScan.next () → 读取最后 2 行,返回批次:name=[孙七, 周八],department=[人事部, 研发部],salary=[13000, 20000]
    Select 过滤掉孙七 → Project 投影 → Sort 存入缓冲区。
  • 全量排序
    SeqScan.next () 返回空批次,Sort 确认数据拉取完毕。此时缓冲区共 4 行数据,一次性执行全量降序排序,得到最终有序结果:王五(22000) → 周八(20000) → 张三(18000) → 赵六(16000)
    排序完成后,Sort 按批次大小拆分结果,返回第 1 批:name=[王五, 周八],salary=[22000, 20000]
第 2 次调用 Sort.next()

直接从排序后的结果缓冲区返回第 2 批:name=[张三, 赵六],salary=[18000, 16000]

第 3 次调用 Sort.next()

缓冲区读取完毕,返回空批次,查询结束。

3. 收尾:递归 close()

自上而下逐层释放资源,清空批次缓冲区,关闭数据文件。

(3)优缺点

优点:大幅减少函数调用次数,适配海量数据扫描;紧凑循环处理数组,编译器可深度优化,CPU缓存利用率高。

缺点:小批量轻量查询存在批次等待开销,延迟高于迭代器模型。

相关推荐
Go Work1 小时前
实体类字段用 @Value 赋了默认值,取出来还是 null?
经验分享·笔记
白杨尚青1 小时前
C++入门篇(十三):vector(上)——动态数组:构造、空间增长与迭代器失效
开发语言·c++·笔记·算法·stl
弈栈录1 小时前
MySQL 事务、索引与锁:后端开发必须掌握的数据库基础
数据库·后端
9624561 小时前
餐饮 SaaS 优惠券系统架构演进(三):优惠计算引擎——商品级计价、冲突策略与优惠分摊
java·数据库·spring boot
染指11101 小时前
135.Agent-多Agent框架-LangChain多智能体(SubAgents子代理)
数据库·人工智能·设计模式·langchain·agent·agents
传奇开心果编程1 小时前
【ArkUI进阶练中学】第13课:元服务与卡片开发
学习·ui·华为·harmonyos
Elastic 中国社区官方博客2 小时前
错误最多的服务运行正常:使用 ES|QL 从日志进行根因分析
大数据·运维·数据库·sql·elasticsearch·搜索引擎·全文检索
做运维的阿瑞2 小时前
mysql数据库视图笔记:创建、修改、删除与适用场景
数据库·笔记·mysql
Highcharts.js2 小时前
可视化商用图表库对比,如何选择?
前端·javascript·学习·信息可视化·highcharts·前端可视化