这是我参与「第四届青训营」笔记创作活动的的第2天
一、SQL的处理流程
graph LR
Parser --AST--> Analyzer --Logical Plan--> Optimizer --Physical Plan--> Executor
1. Parser
把文本string变成抽象语法树结构(AST)
- 词法分析:提取出token
拆分字符串,提取关键字,字符串常量,数值常量,运算符号等token - 语法分析:token → AST
把词条按照定义的语法规则组装成抽象语法树结构
实现
递归下降 (ClickHouse),Flex和Bison (PostgreSQL),JavaCC (Flink),Antlr (Presto, Spark)
2. Analyzer
输入:AST输出:Logical Plan
- 访问库/表元信息并绑定
- 判断 SQL 是否合理,比如数据库,表和列名是否存在,列的数据类型是否正确
- 将 AST 转换成逻辑计划树(在某些系统中这个工作由一个 Converter 完成)
3. Logical Plan
- 逻辑地描述SQL对应的分步骤计算操作
树中每个节点是是一个算子,定义了对数据集合的计算操作,边代表了数据的流向,从孩子节点流向父节点。算子定义的是逻辑的计算操作,没有指定实际的算法。 - 计算操作:算子(operator)
4. Optimizer
- SQL 是一种声明式语言,用户只描述做什么,没有告诉数据库怎么做
- 查询优化的目标是为 SQL 找到一个正确的且执行代价最小的执行计划
- 查询优化器是数据库的大脑,最复杂的模块,很多相关问题都是 NP 的
- 一般 SQL 越复杂,Join 的表越多,数据量越大,查询优化的意义就越大,因为不同执行方式的性能差别可能有成百上千倍
5. Physical Plan
优化器的输出是一个分布式的物理执行计划。分布式物理执行计划的目标是在单机 Plan 的基础上最小化数据移动和最大化本地 Scan,生成 PlanFragment 树。
Plan Fragment:执行计划子树
- 目标:最小化网络数据传输
- 利用上数据的物理分布(数据亲和性)
- 增加Shuffle算子
6. Executor
按照物理执行计划扫描和处理数据,充分利用机器资源(CPU 流水线,乱序执行,cache,SIMD)
- 单机并行:cache, pipeline, SIMD
- 多机并行:一个fragment对应多个实例
二、查询优化器
查询优化器分类
1. 分类方式一
- Top-down Optimizer
从目标输出开始,从上往下遍历计划树,找到完整的最优执行计划。
例子:Volcano/Cascade, SQLServer - Bottom-up Optimizer
从零开始,由下往上遍历计划树,找到完整的执行计划。
例子:System R, PostgreSQL, IBM DB2
2. 分类方式二
- Rule-based Optimizer (RBO)
根据关系代数等价语义,重写查询
基于启发式规则
会访问表的元信息,不会涉及具体的表数据 - Cost-based Optimizer (CBO)
使用模型估算执行计划的代价,选择代价最小的执行计划。
常见的查询优化器
RBO
优点:实现简单,优化速度快
缺点:不保证得到最优的执行计划
1. 关系代数
- 运算符:Select, Project, Join, Rename, Union
- 等价变换:结合律,交换律,传递性
2. 优化原则
- 列剪裁:从上往下筛选出都需要哪些列
- 谓词下推:将那些条件语句下推,提前过滤数列
- 传递闭包:推导出一些新的过滤条件
- Runtime Filter
CBO
1. 统计信息
原始表统计信息
- 表或者分区级别
- 列级别
推导统计信息
- 选择率(selectivity):对于某一个过滤条件,查询会从表中返回多大比例的数据
- 基数(cardinality):在查询计划中常指算子需要处理的行数
统计信息的收集方式
- 在DDL里指定需要收集的统计信息,数据库会在数据写入时收集或者更新统计信息(因为是实时导入,所以会影响速率)
- 手动执行(若不及时执行,可能会比较旧)
- 动态采样(选择一部分,估算整张表)
2. 执行计划枚举
通常使用贪心算法或者动态规划选出最优的执行计划