在数据库系统设计中,执行计划是对SQL执行流程的形式化描述,包括了SQL执行需要的所有算子以及其执行次序。我们通过“EXPLAIN + SQL”指令可以详细地查看其执行计划,找到性能瓶颈,为我们优化SQL提供方向和依据。本文将从EverDB分布式数据库角度阐述执行计划。
(一)分布式架构执行计划
相比于集中式数据库,分布式数据库拥有大量分片节点,分别负责各自分片的数据计算与存储,那么其执行计划就需要特殊的实现方式。对于中间件架构分布式数据库,通过引入分布式算子(即下文EverDB执行计划节点)实现数据分片存储功能,执行计划解析优化,下发数据分片内部独立计算,协调数据分片之间并发执行,执行结果由中间件进行进一步整合进行分组、排序等操作,是一种高效便捷的实现方式。
EverDB正是基于这种设计思路实现的执行计划。与传统集中式数据库相比,EverDB执行计划使数据库有更高的扩展性,支持更大量级的数据规模,更高并发的数据访问。在处理相同负载压力的前提下,可以充分利用各分片的存储与计算资源,以及并行计算的优势达到更好的性能。
(二)EverDB执行计划
EverDB分布式数据库由Grid调度层、数据节点、配置节点、管理台组成。Grid调度层作为分布式数据库的调度节点,接收并解析SQL,将SQL语句重构改造,支持涉及分片表和非分片表两种类型的执行计划分析。
图1
EverDB的执行计划包括SQL在Grid调度层和后端数据节点的执行流程。Grid调度节点的执行计划,主要涉及逻辑处理层和连接驱动层两个部分,其中逻辑处理层包括词法、语法解析模块,客户端通信模块,普通表/分片表配置、SQL重构改造、执行计划树及计划树节点。其中普通表/分片表配置用于识别SQL是否需要分片处理,并获取分片表的存储地址信息,完成基于分片策略的执行计划构建。连接驱动层是内部连接池和通信协议的处理模块,完美支持MySQL通信协议,负责在执行计划中将请求下推给数据节点。数据节点执行计划的实现方式可以参照MySQL执行计划。
图2
以分片查询为例,EverDB的Grid调度节点的执行计划流程:
SQL解析:客户端处理线程接收到从客户端发来的查询请求,对SQL进行词法语法解析。
SQL重构:根据SELECT查询表的存储信息,可分为普通表和分片表,如果是分片表,需要进一步根据查询条件和数据存储情况,重构优化SQL语句。比如,多分片间的跨节点查询,可通过SQL重构后下推数据节点执行,或者通过建立临时表,迁移小部分数据来降低查询性能损耗。
构建执行计划:SQL经过解析,需要构建对应的执行计划树,即用于维护SQL执行计划的数据结构,由多个执行计划节点构成。执行计划节点是SQL执行过程中每一步操作的执行者,也可以看作一个个线程的执行体,它分为很多类型,用于执行不同的操作,比如内部执行节点、事务执行计划节点、数据迁移执行节点、信息查询节点、信息发送节点、组合排序去重节点等。
运行执行计划:执行计划运行过程中,对于分片表查询,采用多线程并发的方式,加快分布式集群的处理速度。
SQL下推:为将查询请求下推至对应的分片数据节点,EverDB通过通信模块(即图3中的MySQL协议适配、驱动模块)将查询请求以MySQL通信协议的格式封装成数据包,再由连接池分配的连接将数据包发送给数据节点,以完成分片查询请求的下推。
整合结果:数据节点接收到来自调度节点的请求,进行进一步的SQL解析,形成针对表的执行计划。查询计算完成后,数据节点将查询结果反馈至调度节点,由调度节点继续按执行计划树,对所有数据节点返回的分片结果进行归并、排序等操作,将完整的查询结果返回给客户端,完成查询请求。
图3
调度节点在生成执行计划树时,会根据分片规则对语句进行并行执行改造,将重构后的多条SQL由对应的执行计划树叶节点下推至目标实例,由数据节点实例完成该分片的查询执行计划分析。
图4介绍了执行计划叶节点将查询请求下推至数据节点的通讯流程。COM_QUERY是封装了查询语句的协议包,由执行计划树叶节点发送至对应的数据节点进行查询计算。执行计划叶节点以MySQL协议流程接收、解析结果集。图示中结果集返回的协议包及次序为:
ResultSetHead:结果集头包,包含列个数信息;
Field:结果集字段包,包含每一字段具体的信息,结果集每一字段对应一个Field协议包;
所有字段信息发送结束后,后端数据节点发送一个 EOF 协议包,开始行数据的发送;
RowData:结果集行数据包,与Field协议包相同,每一行的数据对应一个行数据包,因此,一次结果集发送可能会包含多个行数据协议包;
所有行数据包发送完毕后,服务端会再发送 EOF 协议包表示结果集发送的结束;
执行计划叶节点收到分片的查询结果后,将各自分片结果交由父级非叶节点对所有分片结果做进一步处理(如归并、排序等),向客户端返回完整的查询数据结果。
图4
(三) 如何查看执行计划?
展示执行计划,只需在查询的SELECT关键字之前增加DBSCALE EXPLAIN。具体语法如下:
DBSCALE EXPLAIN + SELECT查询语句;
结果包含执行计划每一步的执行信息,显示执行节点、执行次序和执行SQL内容,SQL性能好坏也能通过执行计划看出来。用于分析SQL语句和表结构的性能瓶颈。
图5
如上图(图5)示例,执行计划返回结果分为上下两个结果集。第一部分展示的是查询请求从中间层Grid到数据节点的完整执行计划。结果集前两列是SQL在中间层Grid的执行计划,即exec_node字段展示SQL的执行计划树,data_source展示的是每一个分片执行节点涉及的分片数据源。结果集其他字段则展示的是每一条分片查询在各自数据节点上的执行计划,这块与MySQLexplain的返回结果是相同的。第二部分展示的是执行计划在每个执行节点上实际运行的重构后SQL语句,因此可能与从客户端接收到的SQL语句不同。
执行计划中一些重要字段的说明如下:
- exec_node:执行计划树的每一个执行节点。整列展示了完整的执行计划树,以“*”开头表示执行计划树根节点,“-”开头表示执行计划树子节点,其中短横线越长表示节点层数越深。如上文示例包含*MySQLSendNodeid首字母为*号,是此例分片查询执行计划树的根节点。--MySQLFetchNode以“--”开头,是执行计划树的子节点,多个FetchNode并发查询对应数据节点的数据分片,再由SendNode整合多个FetchNode的查询结果。
- data_source:数据源信息。数据源是提供数据库连接用来具体执行客户端请求的数据库实例,即MySQLFetchNode执行查询的实例地址。
- id:查询中执行select子句或操作表的顺序,id相同,执行顺序由上至下;id不同,id值越大优先级越高,越先被执行。
- select_type:查询数据的操作类型,如下表:
SIMPLE | 查询中不包含子查询或者UNION |
PRIMARY | 查询中若包含任何复杂的子部分,最外层查询标记为PRIMARY |
SUBQUERY | 在SELECT或WHERE列表中包含了子查询,该子查询被标记为SUBQUERY |
DERIVED | 在FROM列表中包含的子查询被标记为DERIVED(衍生) |
UNION | 若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中,外层SELECT将被标记为DERIVED |
UNION RESULT | 从UNION表获取结果的SELECT被标记为UNION RESULT |
- table:执行节点所处理的表名。
- type:数据节点在表中找到所需行的方式,又称“访问类型”,表示| All | index | range | ref | eq_ref | const,system | null | 由左至右,由最差到最好。常见类型如下表:
ALL | Full Table Scan, 数据节点将遍历全表以找到匹配的行 |
Index | Full Index Scan,index与ALL区别为index类型只遍历索引树 |
Range | 索引范围扫描,对索引的扫描开始于某一点,返回匹配值域的行,常见于between、<、>等的查询 |
Ref | 非唯一性索引扫描,返回匹配某个单独值的所有行。常见于使用非唯一索引即唯一索引的非唯一前缀进行的查找 |
Eq_ref | 唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配;常见于主键或唯一索引扫描 |
Const、system | 当数据节点对查询某部分进行优化,并转换为一个常量时,使用这些类型访问;如将主键置于where列表中,数据节点就能将该查询转换为一个常量,system是const类型的特例,当查询的表只有一行的情况下,使用system |
NULL | 数据节点在优化过程中分解语句,执行时甚至不用访问表或索引 |
- possible_keys:指出数据节点能使用哪个索引在表中找到行,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用。
- key:显示数据节点在查询中实际使用的索引,若没有使用索引,显示为NULLNote:查询中若使用了覆盖索引,则该索引仅出现在key列表中。
- key_len:表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的。
- ref:表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值。
- rows:表示数据节点根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数。
- Extra:数据节点解决查询的详细信息,尽量避免出现:Using File Sort、Using Temporary。
第二部分包括node_id和sql两个字段:node_id与第一部分中exec_node字段的中括号内序号相关联,表示exec_node每个层次中具体执行SQL语句。具体SQL语句内容则在“sql”字段中显示。
当“sql”字段中出现临时表dbscale_tmp时(dbscale_tmp为EverDB保留字),说明当前SELECT查询涉及跨分片查询,系统性能损耗较高,需要进一步分析SQL语句和表结构性能瓶颈,尽可能避免使用临时表,示例如下。
图6
四 总结
EverDB作为一种典型的基于中间件实现分库分表方案的分布式数据库产品,其执行计划相比于传统集中式数据库的不同之处在于,既包括了SQL在底层各分片表上的执行步骤,也包含proxy如何将SQL进行分布式处理,提高分布式数据库的处理性能,是EverDB基于中间件对执行计划一种特有的实现方式。
EverDB执行计划不管从底层数据节点还是中间层,SQL优化算法方面,还有很多值得优化改进的地方。未来,EverDB会持续精进自身的各项能力,努力成为更出色的国产分布式数据库产品。