Mycat2 执行计划管理
Mycat2的执行计划管理主要作用是管理执行计划,加快SQL到执行计划的转换,并且提供一个方式可以从持久层读取自定义的执行计划.它与缓存结果集不同,它更接近传统分库分表的缓存路由.Mycat2引入这个功能的初衷是Calcite在分析复杂SQL的耗时比较大.整体设计上参考了Oracle,Polardb-X的文档.该功能在v1.18开始提供.
LOGIC SQL
逻辑SQL,其中逻辑一词对应的逻辑库,逻辑表中的逻辑.它描述对逻辑库逻辑表的操作.它在Mycat2里也叫DrdsSQL
,它包含了参数化SQL,参照绑定值,它们的类型,以及该SQL的字段别名.一个逻辑SQL的SQL模板具有多个基线,因为约束不同.
BASELINE
SQL运行计划基线,简称基线.在Mycat2里,基线包含执行计划的'类型',逻辑SQL通过'约束'来校验该BASELINE
是否适用.一个基线可以对应多个执行计划,它们的关系类似接口与实现的关系.而其中不同执行计划可能对不同的参数值具有不同的执行代价.它也可以通过FIX
命令强制绑定一个基线对应一个执行计划,此时是一个基线对应一个执行计划.基线在执行计划管理中最重要的作用是提供稳定的执行计划,这是缓存执行计划后自然得到的效果.如果缓存有一定的失效,更新周期,则可以通过一定条件触发更新,结合新的统计信息,可以发现更优的执行计划.
PLAN
一个执行计划对应一种执行器,它包含计算代价的函数,也能生成执行器实例,它还包含了生成它的'原因',比如通过hint
影响导致的生成了该计划(baseline
中的SQL模板是不带hint
的).
EXECUTOR
执行器是执行计划生成的.它需要使用逻辑SQL的信息(参数值)才可以执行并输出结果集.同时,它在执行的时候可以统计执行的运行时信息,并反馈到统计器.因为参数化之后,selectItem
表达式中可能包含带有?
的情况,所以字段名与参数值相关.结果集输出的字段信息需要使用逻辑SQL的别名信息.最后,它是与参数值类型强相关的,因为表达式计算包含对应的类型转换.
选择缓存的执行计划
其中挑选对于当前参数值具有最小执行成本的过程,实际上是SQL编译优化中代价分析过程的精简版,因为它没有使用复杂的框架进行分析也没有生成执行计划的过程,而是仅仅对已有的执行计划代价计算函数赋值,计算得到代价,从中选择最小的来执行.
服务器预处理语句中的延后编译
对于有些SQL中带有?参数的情况,它们的类型在Mycat2不能推导.Mycat2对于这种情况的处理是延后编译.在真正收到实际参数的时候,在把SQL模板与实际参数结合生成完整的,不带有?的语法树(AST
),然后再进行参数化处理,生成新的SQL模板,参数和SQL模板约束.尽管MySQL在8.0.22
有了相关的类型推导规则,它可以进一步,提早编译即在预处理prepare
阶段就编译,但是Mycat2暂时不实现它.
BASELINE和PLAN在哪里持久化
他们存储在原型库(prototype
),在mycat库下的spm
前缀的表里,一般是spm_baseline
和spm_plan
这两个表.原型库可能被多个Mycat2同时操作访问,涉及修改数据的情况,mycat会开启串行事务进行操作.baseline
和plan
的id
也是全局唯一的,它们的生成方法与mycat的workid
相关.
持久化是否必须的
不是的,但是对于Mycat2来说,执行计划缓存是必须的.因为Mycat2的执行器使用了针对SQL模板生成对应的关系表达式,进一步编译成执行器.如果对于每一条SQL都进行该过程,则性能不佳.如果仅仅进行在Mycat2运行的时候缓存生成的,而Mycat2关闭.重启后,执行计划缓存就丢失了.用户会在第一次使用SQL访问的时候,明显感觉Mycat2很慢.对于这个情况,可以减少一些优化过程,减少编译时间,但是会导致生成的执行计划不佳的情况.所以Mycat2添加了对执行计划持久化的功能.
Accept状态
Mycat2暂时没有实现自动演化,所以获得的执行计划都是Accept=true
的状态.在实现自动演化的执行计划管理中,如果触发了执行计划演化,可能会得到新的执行计划,它可能在一定条件下,理论上比现有的缓存的执行计划更具有性能优势,但是此时该执行计划是没有被验证的(Accept=false
),它还不能设置为Accept=true
.它需要经过一定的验证过程(比如流量灰度),证明它比现有的执行计划确实在一定条件更加才会被设置为Accept=true
.
暂时的问题与局限
用户维护持久化的执行计划的一致性
一方面,因为Mycat2保留了中间件的文件文件配置(ZK也是如此),允许了用户脱离Mycat2的感知直接操作配置文件,所以如果手动更改了表的配置,导致持久化的执行计划与表的信息不一致,则会导致数据查询错误.对于这种情况,需要用户自行删除持久化的执行计划..因为自动型hash的表允许使用SQL来修改表,所以它支持自动删除表相关的执行计划(一般的数据库是在更改表配置后自动删除缓存的执行计划的).
不会自动持久化执行计划
另一方面,Mycat2不会自动进行把内存中的执行计划持久化,这与其他数据库不同,它们在遇上复杂SQL的时候会自动持久化.而Mycat2提供了一个把内存中所有基线及其执行计划持久化的命令.用于准备上生产或者测试的时候保存执行计划.Mycat2在启动时候会自动加载(load)原型库中存在的执行计划.如果遇上不一致的问题,把这涉及的表数据清空即可.
不会自动进行执行计划演化以及淘汰
对于已经缓存的执行计划,Mycat2就会使用已有的(一个)执行计划,不会自动演化新的执行计划(统计信息变化,表配置变化).
手动演化以及淘汰
Mycat2提供了管理执行计划的命令,供用户对执行计划进行操作.值得注意的是,它是涉及两个存储空间的,Mycat2内存缓存以及原型库的持久化存储,对于其中一个Mycat2实例进行的内存操作不会发生在另一个Mycat2实例上.
首先,Mycat2在启动时候一定会加载原型库的存在的执行计划.加载完毕才启动成功.然后,Mycat2就处于运行状态.此后Mycat2将不会自动对原型库中的涉及持久化的执行计划的表进行操作.而是仅仅在Mycat2内存中的缓存进行添加操作(如果遇上没有执行计划的SQL).
如果用户使用执行计划管理命令才会涉及到对原型库的操作.
查询命令
LIST 显示所有执行计划,包含未持久化的,已经持久化的执行计划. 但是不会把持久化的基线/执行计划替换内存中的基线/执行计划
BASELINE LIST
管理命令(它们的参数就是plan_id或者baseline_id)
LOAD 把持久化的基线及其执行计划加载(替换)到内存
BASELINE LOAD xxxxx
BASELINE LOAD_ALL_BASELINES
该命令会在Mycat2启动时候自动执行
把持久化中的基线加载到内存
BASELINE LOAD_ALL_BASELINES
BASELINE PERSIST_ALL_BASELINES
把内存中的所有基线及其执行计划持久化
BASELINE PERSIST_ALL_BASELINES
LOAD_PLAN 把持久化的执行计划加载(替换)到内存
BASELINE LOAD_PLAN xxxxx
PERSIST 把内存中的基线持久化
BASELINE PERSIST xxxxx
PERSIST_PLAN 把内存中的执行计划持久化
BASELINE PERSIST_PLAN xxxxx
CLEAR 清理内存中的基线,但是不会清理持久化的基线
BASELINE CLEAR xxxxx
CLEAR_PLAN 清理内存中的执行计划,但是不会清理持久化的执行计划
BASELINE CLEAR_PLAN xxxxx
DELETE 删除持久化的基线,但是不会清理内存中的执行计划
BASELINE DELETE xxxxx
DELETE_PLAN 删除持久化的执行计划,但是不会清理内存中的执行计划
BASELINE DELETE_PLAN xxxxx
ADD/FIX
根据SQL(带hint)添加执行计划,而FIX命令则是把执行计划与基线绑定.该命令不会自动持久化基线与执行计划.
在创建执行计划的时候遇上没有基线的情况会自动创建对应的基线.如果add/fix命令生成的执行计划与现有缓存的执行计划相同则使用现有的执行计划.
BASELINE ADD /*+ mycat:xxx*/ select 1
BASELINE FIX /*+ mycat:xxx*/ select 1
UNFIX
取消基线与执行计划绑定
参数是基线id
BASELINE UNFIX xxxx
``
### 基于脚本的自动演化
整体来说,手动演化和淘汰可以结合用户编写的SQL脚本(根据一定的情况进行触发)进行调用实现自动演化的效果.它与数据库自动演化的方式的区别是数据库掌握更多的触发条件,比如执行计划占用的内存大小以及整个执行计划管理器的内存空间大小.
### 序列化的执行计划
Mycat2的执行计划支持json格式序列化并反序列化成执行计划.所以可以修改保存在数据库中执行计划来实现修改执行器的行为.