开源分布式数据库PolarDB-X源码解读——PolarDB-X源码解读(四):SQL的一生

本文涉及的产品
云原生数据库 PolarDB PostgreSQL 版,标准版 2核4GB 50GB
云原生数据库 PolarDB MySQL 版,通用型 2核4GB 50GB
简介: 开源分布式数据库PolarDB-X源码解读——PolarDB-X源码解读(四):SQL的一生

前文中,我们给出了组成polardbx-sql的三个部分,并从目录入手介绍了重要模块/目录,最后不加解释的列出了一些关键接口作为调试代码的切入点。本文将从SQL执行角度出发,介绍polardbx-sql(CN)代码中与SQL解析执行相关的关键代码。


一、概述


“SQL的一生”特指从客户端创建连接发送SQL开始,到客户端收到返回结果结束,期间CN代码中发生的故事。与人的一生类似,从不同角度观测“SQL的一生”,有不同的结论,比如:


1)如果把CN看成是一个支持MySQL协议的网络程序,“SQL的一生”代表:接收用户连接请求,建立上下文,接收包含SQL的数据包,内部处理后按照MySQL协议组装数据包返回给客户端。


2) 如果把CN看成是一个SQL解释器,“SQL的一生”代表:接收SQL文本,经过词法解析、语法解析、语义解析得到逻辑计划,优化逻辑计划得到物理计划,执行物理计划得到最终结果。  


3)如果把CN看成是一个任务执行引擎,“SQL的一生”代表:接收用户请求,转换为执行计划,确定调度策略,执行任务,返回结果。


               



如上图所示,从整体上看,CN可以分为协议层、优化器、执行器三部分。“SQL的一生”从协议层开始,协议层负责接受用户连接请求,建立连接上下文,将用户发来的数据包转换为SQL语句,交给优化器生成物理执行计划。物理执行计划中包含本地执行的算子和下发给DN的物理SQL,执行器首先下发物理SQL到DN,然后汇总DN返回的结果交给本地执行的算子处理,最后将处理结果返回给协议层,按照MySQL协议封装成数据包后发送给用户。


下文中,会展开介绍协议层、优化器、执行器三部分在SQL解析执行过程的实现细节。


*由于篇幅关系,本文仅包含polardbx-sql(CN)相关内容,polardbx-engine(DN)相关内容留在后续文章中介绍。


二、 协议层


协议层首先要完成的工作是监听网络端口,等待用户建立连接,CN启动流程中介绍过,这部分逻辑在NIOAcceptor的构造函数中,每个CN进程只启动一个NIOAcceptor用于监听网络端口。


       


收到客户端发起的TCP连接请求后,协议层在NIOAcceptor#accept中将TCP连接绑定到一个NIOProcessor上,每个NIOProcessor会启动两个线程,分别用于读/写TCP数据包,读/写线程的实现封装在NIOReactor中。


同时,NIOAcceptor#accept中还将NIOProcessor和一个FrontendConnection对象绑定在一起。FrontendConnection中封装了MySQL协议处理逻辑和Session Context相关信息,有ServerConnectionManagerConnection两个具体实现,ServerConnection用于处理客户端发送的SQL,ManagerConnection用于处理一些内部管理命令。


ServerConnection中解析MySQL协议数据包的逻辑封装NIOHandler中,有FrontendAuthorityAuthenticatorFrontendCommandHandler两个实现,分别用于鉴权和命令执行。


具体过程是,NIORreactor调用AbstractConnection#read完成ssl解析、大包重组等操作,得到具体要处理的数据包,然后调用FrontendConnection#handleData从线程池中获取一个新的线程,最后调用FrontendConnection#handler#handle接口完成鉴权和数据包解析。


             


如上图所示,FrontendCommandHandler#handle中根据Command类型,调用不同的处理函数,最常用的Command是COM_QUERY,所有DML/DDL语句,只要没有通过PreparedStatement执行,Command都是COM_QUERY,对应的处理函数是FrontendConnection#query


FrontendConnection#query方法继续解析数据包,得到SQL文本,传入ServerQueryHandler#queryRaw根据SQL种类分类执行,如果客户端发送的是MySQL Multi-Statement那么这里会将按照语法将多语句切开,分别下发执行。另外这里有个细节是ServerQueryHandler#executeStatement方法会为每个SQL语句生成一个唯一的traceId,traceId最终会出现在各种日志文件和实际下发的物理SQL的HINT中(从DN的binlog中可以获取到),用来排查问题十分方便。


常见的DML/DDL语句的处理逻辑封装在ServerConnection#innerExecute方法中,处理过程可以分为优化执行和返回数据包两部分。TConnection#executeQuery中封装了优化执行部分的逻辑,其中Planner#plan为优化器入口,PlanExecutor#execute为执行器入口。


执行结果包装成ResultCursor对象,在ServerConnection#sendSelectResult中读取结果,组装成COM_QUERY Response数据包返回给客户端。


执行过程中,ExecutionContext作为Session Context记录了执行计划属性/SQL参数/HINT等上下文信息,在三个模块之间传递。


三、优化器


和所有数据库一样,优化器模块的主干流程中包含Parser/Validator/SQL Rewriter(RBO)/Plan Enumerator(CBO)四个基础步骤,在此之上polardbx-sql结合自身特点增加了三个步骤:


 Plan Management:包含Plan Cache和执行计划演进,用于消除执行计划生成带来的额外开销,和通过灰度演进避免CBO版本升级导致查询性能回退。  


 Mpp Planner:在Plan Enumerator之后增加一个阶段,针对MPP模式的执行计划,基于数据分区情况,减少shuffle。


 Post Planner:结合参数进行分区裁剪,根据计划中每张表的裁剪结果继续执行下推,用于处理因为参数化无法进一步优化下推的场景。


                 


SQL在优化器中需要经过Parser--> Plan Management-->Validator-->SQL Rewriter(RBO)-->Plan Enumerator(CBO)-->Mpp Planner-->Post Planner七步处理,入口为Planner#plan,以下将展开介绍各个步骤中的关键接口。


四、Parser


Parser实现基于阿里巴巴开源的连接池管理软件Druid中的Parser组件,是一个手工编写的解析器,用于将SQL文本转换为抽象语法树(AST)。Parser本身包含用于词法解析的MySqlLexer和语法解析的MySqlStatementParser,为何这样划分超出了本文的范围,通过一个例子简单说明下。与我们阅读一句话类似,解析SQL时首先需要SQL文本切分为多个“单词”(Token),比如一条简单的查询语句。


SELECT * FROM t1 WHERE id > 1;


会被切分为如下Token


SELECT (keyword), * (identifier), FROM (keyword), t1 (identifier), 
WHERE (keword), id (identifier), > (gt), 1 (literal_int), ; (semicolon)


这个切分“单词”的过程就是词法解析。接下来,语法解析会按顺序读取所有Token,判断是否满足某个SQL子句语法的同时,生成AST,SELECT语句结果解析生成的对象是SqlSelect,如下图所示:


           


细心的同学可能注意到了,SqlSelect本身和其中成员变量的类型SqlNode都在polardbx-calcite包而不是polardbx-parser包中,原因是polardbx-sql使用了Apache Calcite作为优化器框架,整个框架与AST数据结构强绑定,因此SQL解析过程首先得到Druid的AST对象,然后通过一个visitor转换为SqlNode。代码在FastSqlToCalciteNodeVisitor,其中也包含了一些语法改写和权限校验。


另外,为了更好的支持Plan Cache,所有SQL需要首先进行参数化,也就是使用占位符替换SQL文本中的常量参数,将SQL文本转换为参数化SQL+参数列表,比如,SELECT*FROM t1 WHERE id>1会被转换为SELECT*FROM t1 WHERE id>?和一个参数1,这样做的好处是相同模版不同参数的SQL可以命中同一条Plan Cache,效率更高。当然也有缺点,当参数不同时整个查询的代价也不同,可能需要使用不同的计划,这个问题在Plan Management中得到了解决。参数化相关代码位于DrdsParameterizeSqlVisitor,实现上依然是一个visitor,返回结果封装在SqlParameterized对象中。


五、Plan Management & Plan Cache 


从数据结构上讲,Plan Cache可以认为是一个以SQL模版、参数信息、元数据版本等信息为key,执行计划为value的一个map。用途是减少重复优化相同SQL模版带来的性能开销,以及结合执行计划演进消除可能由于版本升级带来的性能回退。关于Plan Management的详细介绍可以参考PolarDB-X优化器核心技术~执行计划管理


调用Plan Cache和Plan Management的逻辑封装在Planner#doPlan中,Plan Cache的实现代码在PlanCache#get,Plan Management的实现代码在PlanManager#choosePlan


实际上,Plan Cache的实现类似Java中的Map.computIfAbsent():如果map中存在已经生成好的执行计划,则直接返回执行计划;如果不存在则生成一个执行计划保存在map中然后返回这个执行计划。也就是说经过Plan Management&Plan Cache之后一定会拿到一个执行计划,Validator/SQL Rewriter/Plan Enumerator /Mpp Planner四个步骤其实是在Plan Cache内部被调用的,Post Planner由于依赖具体参数,不能Cache,需要拿到执行计划之后调用。


六、 Validator 


早期的数据库实现经常在AST上直接进行查询优化,但由于AST缺少关系代数算子的层次结构,难以写出相互正交的优化规则,会导致所有优化逻辑堆叠在一起,维护困难。现代数据库实现通常都会在validating或者binding过程中将AST转换为关系代数算子组成的算子树作为逻辑计划,polardbx-sql也不例外。关于实现了哪些算子可以参考执行计划介绍


AST到逻辑计划的转换分为两步,入口在Planner#getPlan,首先在SqlConverter#validate中进行语义检查,包括名字空间校验,类型校验等步骤,比较特别的一个点是SqlValidatorImpl#performUnconditionalRewrites中包含了一些对AST改写的内容,主要用于屏蔽相同语义的不同语法结构。然后SqlConverter#toRel中包含了将用SqlNode对象保存的AST转换为使用RelNode对象保存的逻辑计划,转换过程比较复杂这里不做展开。


七、SQL Rewriter


SQL Rewriter是polardbx-sql的RBO组件,工作内容是使用固定规则组对逻辑计划进行优化。polardbx-sql中RBO优化可以分为两个部分,一个是执行传统的关系代数优化(比如谓词推导、LEFT JOIN转INNER JOIN等),另一个是完成部计算下推工作(更多内容参考PolarDB-X优化器核心技术~计算下推)。


调用RBO的代码封装在Planner#optimizeBySqlWriter,RBO框架基于HepPlanner,实现了大量优化规则,规则分组信息保存在RuleToUse中,规则组执行的先后顺序记录在SQL_REWRITE_RULE_PHASE中。  


         


RBO规则要实现三个固定内容,匹配的子树结构、优化逻辑、返回变换后的计划。上图展示的是用于下推JOIN的规则,70-72行在构造函数中指明这个规则匹配的是LogicalJoin下挂两个LogicalView的算子树,当RBO框架匹配到这种子树时会调用规则的onMatch接口,规则完成优化后,调用RelOptRuleCall.transformTo返回变换后的计划。


RBO框架按照组内乱序执行,组间串行执行的方式执行SQL_REWRITE_RULE_PHASE列出的所有规则,每条规则都会反复匹配,直到某一轮匹配后,一组内的规则全都没有命中,则认为该组执行结束。


八、Plan Enumerator


Plan Enumerator是polardbx-sql的CBO组件,工作是生成/枚举物理执行计划,并根据代价选出最合适的物理执行计划,具体包括Join Reorder、索引选择、物理执行计划选择等,详细介绍参考PolarDB-X CBO优化器技术内幕


调用CBO的代码封装在Planner#optimizeByPlanEnumerator中,CBO框架基于VolcanoPlanner框架,与RBO不同,CBO规则无需提前分组,所有用到的规则都保存在RuleToUse#CBO_BASE_RULE中。与RBO规则相同,CBO规则的执行流程也是匹配一颗子树然后返回变换后的计划,区别在于CBO不会直接用新生成的计划替换原来的子树,而是将生成的新的执行计划保存在RelSubset中,后续从RelSubset中选出代价最低的一个计划,代码入口在VolcanoPlanner#findBestExp


九、Mpp Planner


Mpp Planner是polardbx-sql针对MPP执行计划增加的优化阶段,负责根据数据分布生成exchange算子,减少冗余的数据shuffle。代码入口在Planner#optimizeByMppPlan,生成MppExchange算子的代码在MppExpandConversionRule#enforce。  


十、Post Planner


Post Planner主要用于带入实际参数进行分区裁剪后,根据下发的分片情况二次下推执行计划。假设表r和表t在一个table group中,拆分字段都是id,考虑下面的SQL。


SELECT*FROMr JOIN t ON r.name = t.name WHERE r.id = 0 AND t.id = 1;


RBO / CBO 中看到的是参数化之后的计划,类似

SELECT * FROM r JOIN t ON r.name = t.name WHERE r.id = ? AND t.id = ?;

由于没有分区键上的相等条件,无法确定所需的数据是否落在相同的partition group上,但是结合参数进行分区裁剪之后,就会发现其实数据都落在0号分片上,是一条单分片Join语句,可以直接下发。


PostPlanner在Planner中被调用,实现代码在PostPlanner#optimize中。


十一、执行器


                 


PolarDB-X执行器支持两种执行模型,传统的Volcano迭代模型和支持Pipeline向量化的push模型,两种模型各有优劣详细介绍参考PolarDB-X面向HTAP的混合执行器。执行计划进入执行器后存在三条可选的执行链路:Cursor、Local、Mpp。Cursor链路用于执行DML/DDL/DAL语句,Local、Mpp链路用于执行DQL语句。Cursor 链路仅支持Volcano迭代模型,Local链路同时支持两种模型,Mpp链路在Local链路的基础上增加了多机任务调度。


执行器入口代码在PlanExecutor#execByExecPlanNodeByOne,之后在ExecutorHelper#execute中根据优化器确定的执行模式选择执行链路。以下以Cursor链路为例介绍计划执行的整体流程。


             


Cursor链路中,首先根据执行计中的算子找到对应的handler,代码位置在CommandHandlerFactoryMyImp#getCommandHandler,handler负责将当前算子转换为一个Cursor接口的具体实现,并且嵌套的调用CommandHandlerFactoryMyImp#getCommandHandler接口将整个执行计划转换为一颗Cursor树。ResultSetUtil#resultSetToPacket中调用Cursor.next接口获取全部执行结果返回给用户。


             


               


Local链路的执行入口在SqlQueryLocalExecution#start,首先在LocalExecutionPlanner#plan中将执行计划切分为多个Pipeline,切分的具体逻辑在LocalExecutionPlanner#visit,然后为每个Pipeline生成一个包含具体执行逻辑的Driver放入调度队列,执行逻辑封装在Executor或者ConsumerExecutor中,分别代表迭代模型和push模型。执行结果封装在SmpResultCursor中,与Cursor相同,ResultSetUtil#resultSetToPacket中调用Cursor.next接口获取全部执行结果返回给用户。


Mpp链路的执行入口在SqlQueryExecution#start,首先在PlanFragmenter#buildRootFragment中根据MppPlanner插入的Exchange算子将执行计划切分成多个Fragment,之后在SqlQueryScheduler#schedule中调用StageScheduler生成和下发并行计算任务,任务信息封装在HttpRemoteTask对象中,并行计算任务下发到执行节点后的执行链路与Local链路相同。关于Mpp链路的更多原理介绍请参考PolarDB-X并行计算框架


十二、小结


本文从SQL执行的角度出发,介绍了协议层、优化器、执行器的关键代码。协议层负责网络连接管理、数据包到SQL和执行结果到数据包的转换,第一小节介绍协议层的执行流程,并给出了基于NIO的网络连接管理、SSL协议解析、鉴权、协议解析、大包重组等内容的代码入口。优化器负责SQL到执行计划的转换,转换过程包含七个阶段Parser-->Plan Management-->Validator-->SQL Rewriter(RBO)-->Plan Enumerator(CBO)-->Mpp Planner-->Post Planner,第二小节简单介绍了每个阶段的工作内容并给出了代码入口。执行器负责根据执行计划获得最终执行结果,支持迭代模型和Pipeline向量化模型,分为Cursor、Local和MPP三条执行链路,第三小节介绍了各个链路的基本流程和关键入口。



相关实践学习
快速体验PolarDB开源数据库
本实验环境已内置PostgreSQL数据库以及PolarDB开源数据库:PolarDB PostgreSQL版和PolarDB分布式版,支持一键拉起使用,方便各位开发者学习使用。
相关文章
|
1月前
|
缓存 NoSQL Ubuntu
大数据-39 Redis 高并发分布式缓存 Ubuntu源码编译安装 云服务器 启动并测试 redis-server redis-cli
大数据-39 Redis 高并发分布式缓存 Ubuntu源码编译安装 云服务器 启动并测试 redis-server redis-cli
53 3
|
6月前
|
关系型数据库 分布式数据库 数据库
【PolarDB开源】PolarDB-X源码解读:分布式事务处理机制揭秘
【5月更文挑战第20天】PolarDB-X,PolarDB家族的一员,专注于大规模分布式事务处理,采用2PC协议保证ACID特性。源码解析揭示其通过预提交、一致性快照隔离和乐观锁优化事务性能,以及利用事务日志进行故障恢复。深入理解其事务处理机制对开发者掌握分布式数据库核心技术至关重要。随着开源社区的发展,更多优化方案将涌现,助力构建更强大的分布式数据库系统。
220 6
|
4月前
|
缓存 运维 关系型数据库
数据库容灾 | MySQL MGR与阿里云PolarDB-X Paxos的深度对比
经过深入的技术剖析与性能对比,PolarDB-X DN凭借其自研的X-Paxos协议和一系列优化设计,在性能、正确性、可用性及资源开销等方面展现出对MySQL MGR的多项优势,但MGR在MySQL生态体系内也占据重要地位,但需要考虑备库宕机抖动、跨机房容灾性能波动、稳定性等各种情况,因此如果想用好MGR,必须配备专业的技术和运维团队的支持。 在面对大规模、高并发、高可用性需求时,PolarDB-X存储引擎以其独特的技术优势和优异的性能表现,相比于MGR在开箱即用的场景下,PolarDB-X基于DN的集中式(标准版)在功能和性能都做到了很好的平衡,成为了极具竞争力的数据库解决方案。
|
3月前
|
存储 缓存 负载均衡
【PolarDB-X 技术揭秘】Lizard B+tree:揭秘分布式数据库索引优化的终极奥秘!
【8月更文挑战第25天】PolarDB-X是阿里云的一款分布式数据库产品,其核心组件Lizard B+tree针对分布式环境优化,解决了传统B+tree面临的数据分片与跨节点查询等问题。Lizard B+tree通过一致性哈希实现数据分片,确保分布式一致性;智能分区实现了负载均衡;高效的搜索算法与缓存机制降低了查询延迟;副本机制确保了系统的高可用性。此外,PolarDB-X通过自适应分支因子、缓存优化、异步写入、数据压缩和智能分片等策略进一步提升了Lizard B+tree的性能,使其能够在分布式环境下提供高性能的索引服务。这些优化不仅提高了查询速度,还确保了系统的稳定性和可靠性。
89 5
|
3月前
|
存储 SQL 运维
“震撼发布!PolarDB-X:云原生分布式数据库巨擘,超高并发、海量存储、复杂查询,一网打尽!错过等哭!”
【8月更文挑战第7天】PolarDB-X 是面向超高并发、海量存储和复杂查询场景设计的云原生分布式数据库系统
105 1
|
3月前
|
C# UED 定位技术
WPF控件大全:初学者必读,掌握控件使用技巧,让你的应用程序更上一层楼!
【8月更文挑战第31天】在WPF应用程序开发中,控件是实现用户界面交互的关键元素。WPF提供了丰富的控件库,包括基础控件(如`Button`、`TextBox`)、布局控件(如`StackPanel`、`Grid`)、数据绑定控件(如`ListBox`、`DataGrid`)等。本文将介绍这些控件的基本分类及使用技巧,并通过示例代码展示如何在项目中应用。合理选择控件并利用布局控件和数据绑定功能,可以提升用户体验和程序性能。
62 0
|
4月前
|
存储 关系型数据库 MySQL
深度评测:PolarDB-X 开源分布式数据库的优势与实践
本文对阿里云开源分布式数据库 PolarDB-X 进行了详细评测。PolarDB-X 以其高性能、强可用性和出色的扩展能力在云原生数据库市场中脱颖而出。文章首先介绍了 PolarDB-X 的核心产品优势,包括金融级高可靠性、海量数据处理能力和高效的混合负载处理能力。随后,分析了其分布式架构设计,包括计算节点、存储节点、元数据服务和日志节点的功能分工。评测还涵盖了在 Windows 平台通过 WSL 环境部署 PolarDB-X 的过程,强调了环境准备和工具安装的关键步骤。使用体验方面,PolarDB-X 在处理分布式事务和实时分析时表现稳定,但在网络问题和性能瓶颈上仍需优化。最后,提出了改进建
7000 2
|
4月前
|
关系型数据库 分布式数据库 数据库
PolarDB产品使用问题之将RDS切换到PolarDB-X 2.0时,代码层的SQL该如何改动
PolarDB产品使用合集涵盖了从创建与管理、数据管理、性能优化与诊断、安全与合规到生态与集成、运维与支持等全方位的功能和服务,旨在帮助企业轻松构建高可用、高性能且易于管理的数据库环境,满足不同业务场景的需求。用户可以通过阿里云控制台、API、SDK等方式便捷地使用这些功能,实现数据库的高效运维与持续优化。
|
4月前
|
关系型数据库 分布式数据库 数据库
PolarDB-X源码解析:揭秘分布式事务处理
【7月更文挑战第3天】**PolarDB-X源码解析:揭秘分布式事务处理** PolarDB-X,应对大规模分布式事务挑战,基于2PC协议确保ACID特性。通过预提交和提交阶段保证原子性与一致性,使用一致性快照隔离和乐观锁减少冲突,结合故障恢复机制确保高可用。源码中的事务管理逻辑展现了优化的分布式事务处理流程,为开发者提供了洞察分布式数据库核心技术的窗口。随着开源社区的发展,更多创新实践将促进数据库技术进步。
77 3
|
5月前
|
关系型数据库 MySQL 数据库
上手体验 PolarDB-X 数据库
PolarDB-X,一款高性能云原生分布式数据库。
116 2

热门文章

最新文章

相关产品

  • 云原生数据库 PolarDB