组件Parser
-
String(输入,可是文本或sql语句) -> AST(输出) AST(abstract syntax tree 抽象语法树)
- 词法分析: 拆分字符串,得到关键词(select、from...)、数值常量、字符串常量、运算符号(+、-)等token
- 语法分析: 将token按SQL定义的语法组成AST node(节点),最终将节点构造得到一个AST
-
实现:递归下降(ClickHouse),Flex和Bison(PostgreSQL),JavaCC(Flink),Antlr(Presto,Spark)
组件Analyzer 和 Logical Plan
- AST(输入, 逻辑语法树) - > Logical Plan(输出, 逻辑执行计划)
- Analyzer
- 检查并绑定Database, Table, Column等元信息
- SQL的合法性检查,比如min/max/avg的输入是数值
- AST -> Logical Plan
- Logical Plan
- 逻辑地描述SQL对应的分步骤计算操作
- 计算操作:算子( operator ) :scan算子、join算子...... 简单示例(AST->Logical Plan):
转换结果如下:
※查询优化(Optimizer)
- SQL 是一种声明语言,用户只描述做什么,但没有告诉数据库怎么做
- 目标:找到一个正确且执行代价最小的物理执行计划
- 查询优化器是数据库的大脑,最复杂的模块,很多相关问题都是NP(无法很好的求得最优解)的
- SQL越复杂,join的表越多,数据量越大,查询优化的意义就越大。
组件Executor 和 Physical Plan
- Executor
- 单机并行:cache,pipeline,SIMD
- 多机并行:一个fragment 对应多个实例
- Plan Fragment:执行计划子树
- 目标:最小化网络数据传输
- 利用上数据的物理分布(数据亲和性)
- 增加Shuffle算子
示意图:
小结
- One SQL rules big data all
- SQL 需要依次经过 Parser、Analyzer、Optimizer、Executor的处理
- 查询优化器是数据库的大脑,在大数据场景下对查询性能至关重要
- 查询优化器需要感知数据分布,充分利用数据亲和性
- 查询优化器按照最小化网络数据传输的目标把逻辑计划拆分成多个物理计划片段
0x02 查询优化器
按照遍历逻辑执行计划树的顺序划分:
- Top-down Optimizer
- 从目标输出开始,由上往下遍历计划树,找到完整的最优执行计划
- 应用实例:Volcano/Cascade, SQLServer
- Bottom-top Optimizer
- 从零开始,由下往上遍历计划树,找到完整的执行计划
- 应用实例:System R, PostgreSQL, IBM DB2
根据优化方法划分(重点):
- RBO(Rule-based Optimizer):基于规则的优化器
- 根据关系代数等价语义,重写查询
- 基于启发式规则
- 会访问表的元信息(catalog),不会涉及具体的表数据(data)
- CBO(Cost-based Optimizer):基于代价的优化器
- 使用一个模型估算执行计划的代价,选择代价最小的执行计划
RBO - 关系代数
优化原则:
- Read data less and faster(I/O)
- Transfer data less and faster(Network)
- Process data less and faster(CPU & Memory)
优化思路:
- 列裁剪:对应算子使用不到的列在计算时不需要读取或保留,将列裁剪掉减少I/O和内存的占用,为后续优化带来便利
- 谓词下推:谓词-where中的一些表达式,某些场景下,将where中的限制条件在join之前进行,减少传输的数据量
- (高级一点)传递闭包:需要程序有部分的语法分析能力,where语句中对A单表数据进行了filter过滤,若A的此项为与另一表B的关联数据项,则对关联表B也进行相关filter过滤,使join的数据显著减少
- (更高级一点)Runtime Filter:将join的一个经过filter过滤的数据构建一个hash表,同时根据连接的key得到集合的特性(数据的范围...)在运行时候传递到join的另一个输入端,形成一个filter(min-max,in-list,bloom filter)
小结
- 主流RBO实现一般都有上百条基于经验归纳得到的优化规则
- 优点:实现简单,优化速度快
- 缺点:不保证得到最优的执行计划
CBO
概念
- 使用一个模型估算执行计划的代价,选择代价最小的执行计划
- 执行计划的代价等于所有算子的执行代价之和
- 通过RBO得到所有可能的等价执行计划
- 算子代价:CPU、内存、磁盘I/O、网络I/O等代价
统计信息
- 原始表统计信息
- 表或分区级别:行数、行平均大小、表在磁盘中占用了多少字节
- 列级别:min、max、num nulls、num not nulls、num distinct value(NDV)、histogram等
- 推导统计信息
- *选择率(selectivity):*对于某一个过滤条件,查询会从表中返回多大比例的数据
- *基数(cardinality):*在查询计划中常指算子需要处理的行数
- 准确的基数,远比代价模型本身重要
统计信息收集方式
- 在DDL里指定需要收集的统计信息,数据库会在数据写入时实时收集或更新统计信息(会影响实时操作速率)
- 手动执行命令(explain analyze statement),触发数据库收集或更新统计信息(统计信息可能会过时)
- 动态采样,根据表的部分数据估算出全表的数据
小结
- CBO使用代价模型和统计信息去估算执行计划的代价
- CBO使用贪心或动态规划算法寻找最优执行计划(累计代价最小)
- 大数据场景下CBO对查询性能优化效果明显
思考
很多时候,对于一项技术,不能仅仅停留于会使用就行,一直以来,sql的curd写的太多,缺少有时间去思考sql的一些基础的东西,多问问自己几个为什么,他为什么要这么设计,为什么要这样做。这些会促使我们对一项技术的更底层进行探索和钻研,也只有这样,才能真正精进自己的技术水平