Mycat2 注释
注释的解析语法
query: [ '/*+' hint [, hint]* '*/' ] select
select:
SELECT [ '/*+' hint [, hint]* '*/' ] [ ALL | DISTINCT ]
{ * | projectItem [, projectItem ]* }
FROM tableExpression
[ WHERE booleanExpression ]
[ GROUP BY { groupItem [, groupItem ]* } ]
[ HAVING booleanExpression ]
selectWithoutFrom:
SELECT [ ALL | DISTINCT ]
{ * | projectItem [, projectItem ]* }
projectItem:
expression [ [ AS ] columnAlias ]
| tableAlias . *
tableExpression:
tableReference [, tableReference ]*
| tableExpression [ NATURAL ] [ ( LEFT | RIGHT | FULL ) [ OUTER ] ] JOIN tableExpression [ joinCondition ]
| tableExpression CROSS JOIN tableExpression
| tableExpression [ CROSS | OUTER ] APPLY tableExpression
joinCondition:
ON booleanExpression
| USING '(' column [, column ]* ')'
|tableReference:
tablePrimary
[ [ AS ] alias [ '(' columnAlias [, columnAlias ]* ')' ] ]
|tablePrimary:
| [ [ catalogName . ] schemaName . ] tableName
| '(' TABLE [ [ catalogName . ] schemaName . ] tableName ')'
tablePrimary [ '/*+' hint [, hint]* '*/' ]
|values:
VALUES expression [, expression ]*
groupItem:
expression
| '(' ')'
| '(' expression [, expression ]* ')'
SELECT UNION [ALL | DISTINCT] SELECT ...
特殊地,SQL头部可以编写注释
可以留意到[ '/*+' hint [, hint]* '*/' ]
在select关键字之后,tablePrimary(即表名)之后
在mycat2里,建议在SQL头部写注释,其他语法单元写注释更多是为了辅助分析3
hint: 'MYCAT' ':' HINT_TEXT
HINT_TEXT
是一段文本,没有固定格式
这里的HINT语法并非指注释只能在上述的语法单元中编写,而是说,Mycat2在这些语法单元捕获这些注释,进行处理,不在这些语法单元的注释会被忽略
HINT的处理组件
Mycat2支持注释控制Mycat2对SQL的处理过程,在分库分表型数据库中,注释的生效的地方有三处地方
- SQL优化器
- 执行器
- 存储节点即MySQL
本文描述第一种注释,它控制SQL编译器生成执行器
注释应用对象的定位
注释在作用范围上习惯上分为两大类
- 全局的,
- 针对某个节点的(可能结合父子节点判断).
定位的方法
- 路径法,根据遍历顺序或者名称路径找到该节点
- 唯一名称法,在SQL中标记唯一的名称,在遍历时候找到带有该名称的节点.
于是有下面的具体的定位方法.
QB_NAME
参考MySQL的优化器注释Optimizer Hints
,Mycat2也使用QB_NAME
给表与查询块(Select
语法元素)建立名称.注释命令通过该名称定位生效的作用域.
格式
QB_NAME(name)
name
为符合MySQL词法的标识符
表注释
SELECT * FROM db1.travelrecord /*+MYCAT:QB_NAME(SEL$1)*/
SELECT * FROM db1.travelrecord /*+MYCAT:QB_NAME(SEL$2)*/
union all
SELECT * FROM db1.travelrecord /*+MYCAT:QB_NAME(SEL$4)*/
SELECT * FROM db1.travelrecord t where t.id in (SELECT id FROM db1.user /*+MYCAT:QB_NAME(SEL$4)*/ where user_id = 1)
查询块注释
SELECT /*+MYCAT:QB_NAME(SEL$1)*/ * FROM db1.travelrecord
SELECT /*+MYCAT:QB_NAME(SEL$1)*/ * FROM db1.travelrecord
union all
SELECT /*+MYCAT:QB_NAME(SEL$3)*/ * FROM db1.travelrecord /*+MYCAT:QB_NAME(SEL$4)*/
SELECT /*+MYCAT:QB_NAME(SEL$1)*/ * FROM db1.travelrecord t where t.id in (SELECT id FROM db1.user /*+MYCAT:QB_NAME(SEL$4)*/ where user_id = 1)
自动生成的QB_NAME注释
Mycat2的SQL编译器在处理语法树的时候会在Select
语法单元或表语法单元自动添加QB_NAME
注释,它们生成的参数是以SEL$
为前缀,以1
为开始值,Select
语法单元或表语法单元出现的顺序,当表有别名的时候,则忽略该值,使用别名作为QB_NAME
的参数.
//以该无注释的SQL为例
SELECT * FROM db1.travelrecord
union all
SELECT * FROM db1.travelrecord
//自动生成QB_NAME
SELECT /*+MYCAT:QB_NAME(SEL$1)*/ * FROM db1.travelrecord /*+MYCAT:QB_NAME(SEL$2)*/
union all
SELECT /*+MYCAT:QB_NAME(SEL$3)*/ * FROM db1.travelrecord /*+MYCAT:QB_NAME(SEL$4)*/
//当有别名的时候
SELECT /*+MYCAT:QB_NAME(SEL$1)*/ * FROM db1.travelrecord t /*+MYCAT:QB_NAME(t)*/ where t.id in (SELECT /*+MYCAT:QB_NAME(SEL$3)*/ id FROM db1.user /*+MYCAT:QB_NAME(SEL$4)*/ where user_id = 1)
无需QB_NAME
上一段描述了QB_NAME
的作用,实际上是为了定位注释的参数是关系表达式中哪个对象.实际上QB_NAME
不是一定需要的,比如
SELECT /*+MYCAT:USE_HASH_JOIN(travelrecord,user)* FROM db1.travelrecord t where t.id in (SELECT id FROM db1.user where user_id = 1)
这个情况下,我们只需探测join
节点下的两个表是否为travelrecord
和user
即可定位该JOIN
节点就是应用USE_HASH_JOIN
注释的对象.
SQL编译器对执行优化器注释(物理表达式注释)的处理
SQL通过参数化处理得到带有HINT的参数化SQL与不带HINT的参数化SQL,前者用于生成执行计划,后者用在执行计划管理,起SQL模板的作用,满足该SQL模板的SQL集合都可以使用该执行计划,
首先,带有HINT的参数化SQL经过SQL编译器编译得到逻辑关系表达式,并把SQL语法节点上的HINT绑定到逻辑关系表达式上.
其次,根据SQL的词法作用域对Hint进行传播,从父节点往子节点开始传播,下节点会得到上节点的hint,并执行hint的校验函数,判断节点(关系表达式)是否适用该节点.这样,我们就得到了带有Hint的逻辑关系表达式.
然后,SQL优化器会对逻辑关系表达式进行优化,最终转换成物理关系表达式.其中过程非常复杂,最常见的就是关系表达式之间的上拉下推.所以Hint也有可能跟随这些逻辑关系表达式进行在关系表达式树的位置中发生变化或者因为额外添加或者删除关系表达式导致hint无法生效.所以一般来说,hint作用于关键节点,它不能影响语义,无法生效也不足为奇.
在分库分表数据库中,因为引入了View关系算子,它内部仍然是逻辑关系表达式,它的父节点是可以是逻辑关系表达式也可以是物理关系表达式.已经内部的关系表达式不视为代价优化器可以直接优化的部分,它们是根据Mycat2的RBO规则生成的,虽然它们仍然参与代价计算.
但是考虑到Hint的作用域,考虑以下这一种情况.已经成为View内部的表节点带有的QB_NAME,它们仍然要能被外部的HashJoin的Hint处理器检查到,即Hint的处理过程是跨越View的外部和内部的.
物理表达式注释
Join物理算子注释
该注释支持QB_NAME(别名)定位参数也支持表名直接指定,注释不生效不会报错
格式
use_xxx_join(QB_NAME,QB_NAME)
第一个QB_NAME是代表左表的关系算子,第二个QB_NAME是代表右表的关系算子
use_nl_join(QB_NAME,QB_NAME)(1.18-2021-4-29后)
/*+MYCAT:use_nl_join(n,s) */select * from db1.travelrecord n join db1.company s on n.id = s.id and n.id = 1
plan
MycatNestedLoopJoin(condition=[=($0, $6)], joinType=[inner])
MycatView(distribution=[[db1.travelrecord]])
MycatMatierial
MycatView(distribution=[[db1.company]])
Each(targetName=c0, sql=SELECT * FROM db1_0.travelrecord_0 WHERE (`id` = ?) union all SELECT * FROM db1_0.travelrecord_1 WHERE (`id` = ?))
Each(targetName=c1, sql=SELECT * FROM db1_1.travelrecord_0 WHERE (`id` = ?) union all SELECT * FROM db1_1.travelrecord_1 WHERE (`id` = ?))
Each(targetName=prototype, sql=SELECT * FROM db1.company WHERE (`id` = ?))
...
use_hash_join(QB_NAME,QB_NAME)(1.18-2021-4-29后)
/*+MYCAT:use_hash_join(n,s) */select * from db1.travelrecord n join db1.company s on n.id = s.id and n.id = 1
plan
MycatHashJoin(condition=[=($0, $6)], joinType=[inner])
MycatView(distribution=[[db1.company]])
Each(targetName=c0, sql=SELECT * FROM db1_0.travelrecord_0 WHERE (`id` = ?) union all SELECT * FROM db1_0.travelrecord_1 WHERE (`id` = ?))
Each(targetName=c1, sql=SELECT * FROM db1_1.travelrecord_0 WHERE (`id` = ?) union all SELECT * FROM db1_1.travelrecord_1 WHERE (`id` = ?))
Each(targetName=prototype, sql=SELECT * FROM db1.company WHERE (`id` = ?))
...
use_merge_join(QB_NAME,QB_NAME)(1.18-2021-4-29后)
/*+MYCAT:use_merge_join(n,s) */select * from db1.travelrecord n join db1.company s on n.id = s.id and n.id = 1
plan
MycatSortMergeJoin(condition=[=($0, $6)], joinType=[inner])
MycatMergeSort(sort0=[$0], dir0=[ASC])
MycatView(distribution=[[db1.travelrecord]])
MycatMergeSort(sort0=[$0], dir0=[ASC])
MycatView(distribution=[[db1.company]])
Each(targetName=c0, sql=SELECT * FROM db1_0.travelrecord_0 WHERE (`id` = ?) ORDER BY (`id` IS NULL), `id`)
Each(targetName=c0, sql=SELECT * FROM db1_0.travelrecord_1 WHERE (`id` = ?) ORDER BY (`id` IS NULL), `id`)
Each(targetName=c1, sql=SELECT * FROM db1_1.travelrecord_0 WHERE (`id` = ?) ORDER BY (`id` IS NULL), `id`)
Each(targetName=c1, sql=SELECT * FROM db1_1.travelrecord_1 WHERE (`id` = ?) ORDER BY (`id` IS NULL), `id`)
Each(targetName=prototype, sql=SELECT * FROM db1.company WHERE (`id` = ?) ORDER BY (`id` IS NULL), `id`)
...
no_hash_join(QB_NAME,QB_NAME)(1.18-2021-4-29后)
禁用生成HashJoin