当前位置: 首页 > news >正文

Go语言从零构建SQL数据库(8):执行计划的奥秘

从SQL语句到高效查询:执行计划的奥秘

想象你是一位旅行者,想从北京到上海。你告诉导航软件你的目的地(类似SQL查询),但导航软件需要为你规划具体路线——是走高速公路还是国道?是选择最短距离还是最省时间的路线?这个"路线规划",就像数据库中的执行计划。

SQL查询的挑战

当用户输入一个SQL查询时,他们只是表达了"我想要什么数据",而没有指定"如何获取这些数据"。例如:

SELECT name, age FROM users WHERE salary > 5000

这告诉数据库:我需要薪资超过5000的用户的姓名和年龄。但数据库如何高效地找到这些数据呢?

查询方案1
扫描全表
过滤薪资>5000
返回name,age
查询方案2
使用薪资索引
获取匹配记录
返回name,age
  • 是先读取所有用户记录,再筛选出薪资高的?
  • 还是先通过索引找到高薪用户,再获取他们的姓名和年龄?
  • 如果有几百万用户,两种方法的性能可能相差上千倍!

数据库的"导航系统"

执行计划正是解决这一难题的关键——它将"我要什么数据"转换为"如何高效获取这些数据"的具体步骤。

好的执行计划能带来惊人的性能提升:同样的查询,可能从几分钟缩短到几毫秒。这就是为什么执行计划在数据库引擎中如此重要。

SQL语句
语法分析
语法树AST
逻辑计划生成
逻辑计划
优化器
优化后的逻辑计划
物理计划生成器
物理执行计划
执行引擎
查询结果

两层设计的智慧

执行计划通常分为两个层次:

  1. 逻辑计划:描述查询的逻辑步骤,好比"从北京到上海"
  2. 物理计划:确定具体执行算法,相当于"走G2高速,经过济南,预计用时5小时"
逻辑计划
物理计划A: 顺序扫描
物理计划B: 索引扫描
物理计划C: 哈希连接

这种分层设计非常聪明,原因有三:

  • 关注点分离:先确定要做什么,再决定怎么做
  • 优化灵活性:同一逻辑操作可以有多种物理实现方式
  • 技术升级简化:可以改进物理实现(比如新算法)而不影响上层逻辑

就像你的旅行,先确定要去上海(逻辑目标),再决定是坐飞机、高铁还是自驾(物理实现)。

构建执行计划的核心组件

计划节点:组织成树状结构

执行计划通常构建为树状结构,这种设计很自然地反映了数据处理的流程:

投影: SELECT name, age
过滤: WHERE salary > 5000
表扫描: users表
  • 叶子节点:获取数据的来源(如表扫描)
  • 中间节点:处理数据的操作(如过滤、排序)
  • 数据自下而上流动:从数据源到最终结果

这种树状结构非常清晰,每个节点只关心接收数据、处理数据和输出数据,不需要了解整个查询的复杂性。

接口设计:灵活性的基石

我们为执行计划设计了接口而非具体类型,这带来了巨大的灵活性:

type LogicalPlan interface {PlanType() PlanTypeChildren() []LogicalPlanSchema() *SchemaString() string
}
«interface»
LogicalPlan
+PlanType() : PlanType
+Children() : []LogicalPlan
+Schema() : Schema
+String() : string
LogicalTableScan
+TableName string
+Schema *Schema
LogicalFilter
+Input LogicalPlan
+Condition Expression
LogicalProjection
+Input LogicalPlan
+Expressions []Expression
+OutputSchema *Schema

这种接口设计有几个关键好处:

  1. 扩展性:可以轻松添加新的计划节点类型
  2. 低耦合:不同模块通过接口交互,而非具体实现
  3. 测试便利:可以创建模拟实现进行单元测试

想象一下,如果没有这种接口设计,每添加一种新的操作类型(如窗口函数),就需要修改整个系统的代码。

Schema系统:类型的守护者

执行计划需要知道每个操作产生的数据结构,这就是Schema的作用:

type Schema struct {Columns []*Column
}type Column struct {Name     stringTable    stringDataType DataType
}
1
*
Schema
+Columns []*Column
Column
+Name string
+Table string
+DataType DataType

Schema系统看似简单,但功能强大:

  1. 类型检查:确保操作应用于兼容类型(不能对字符串求平均值)
  2. 列引用解析:当有多表连接时,确定列来自哪个表
  3. 结果描述:告诉客户端返回数据的结构

没有Schema,数据库就像是没有类型检查的语言,可能在运行时才发现致命错误。

表达式系统:计算的引擎

表达式是查询的核心,表示过滤条件、计算列等。设计独立的表达式系统有多重好处:

graph TDA[表达式系统] --> B[列引用: user.age]A --> C[字面量: 5000]A --> D[函数调用: YEAR(birth_date)]A --> E[二元操作: price * quantity]style A fill:#f9f,stroke:#333
  1. 统一表示:无论是简单比较还是复杂函数,都用同一套系统表示
  2. 类型安全:在执行前检查表达式类型是否正确
  3. 优化机会:可以对表达式进行变换和优化

例如,系统可以自动将 price * 0 优化为常量 0,避免不必要的计算。

完整的计划转换流程

当我们把所有组件结合起来,就能实现从SQL到执行计划的完整转换:

SQL文本
词法分析器
语法分析器
语法树AST
计划生成器
逻辑计划
优化器
优化后的逻辑计划
物理计划生成器
物理执行计划
执行引擎
查询结果

这整个流程看似复杂,但每一步都有其不可替代的作用,共同确保数据库能够高效地执行查询。

总结与未来方向

通过这种设计,我们的数据库引擎能够:

  1. 将用户的声明式查询转换为高效的执行步骤
  2. 灵活应对各种查询模式和数据规模
  3. 为后续优化提供坚实基础
当前系统
增加聚合操作
实现基于成本的优化
丰富表达式函数
优化物理执行策略

未来,我们还可以增强系统的多个方面:

  • 支持更多的操作类型(如聚合、排序)
  • 实现基于成本的查询优化
  • 添加更丰富的表达式函数
  • 优化物理执行策略

理解了执行计划的设计理念,我们就能更好地构建高性能、可扩展的数据库系统。下一篇文章,我们将探讨如何实现对SELECT * 查询的支持,进一步完善我们的执行计划生成器。

相关文章:

  • Missashe考研日记-day22
  • 一次性执行多个.sql文件(PostgreSql)
  • kkFileView同名文件修改内容后预览未更新的问题
  • 赛灵思 XCVU3P‑2FFVC1517I XilinxFPGA Virtex UltraScale+
  • 第10篇:Linux程序访问控制FPGA端HEX<三>
  • 一种大位宽加减法器的时序优化
  • C++学习:六个月从基础到就业——面向对象编程:访问控制与友元
  • 提高Qt工作线程的运行速度
  • 深入理解 VMware 虚拟机网络模式:为虚拟化管理铺平道路
  • Java基础系列-ArrayList源码解析
  • 【verilog】Verilog 工程规范编码模板
  • webgl入门实例-07顶点缓冲区基本概念
  • LabVIEW液压系统远程监控与故障诊断
  • 【创新实训个人博客】前端实现
  • 基于Flask的漏洞挖掘知识库系统设计与实现
  • Java语言实现递归调用算法
  • Java课堂6
  • 组合模式实战:用树形结构管理企业组织与文件系统
  • 【PyTorch】PyTorch中的非线性激活函数详解:原理、优缺点与实战指南
  • 自求导实现线性回归与PyTorch张量详解
  • 习近平向第三十四届阿拉伯国家联盟首脑理事会会议致贺信
  • 《歌手》回归,人均技术流,00后整顿职场
  • 缅甸内观冥想的历史漂流:从心理治疗室到“非语言现场”
  • 市场监管总局召开平台企业支持个体工商户发展座谈会
  • 马上评|让查重回归促进学术规范的本意
  • 外交部:中方对美芬太尼反制仍然有效