simple_expr为简单表达式,并不是说它很简单,因为simple_expr也包含了select_with_parens(带括号的select语句)等语句,只能说simple_expr不能像arith_expr和expr等一样递归调用。
expr:expr BETWEEN arith_expr AND arith_expr %prec BETWEEN | expr NOT BETWEEN arith_expr AND arith_expr %prec BETWEEN ;
arith_expr用于between ... and 之间,只包含了算术表达式,没有is,like 以及比较关系表达式。
expr,arith_expr,simple_expr实际上等同于PostgreSQL中的a_expr,b_expr,c_expr。
总之,在语法树规则和词法分析方面,OceanBase大量的借鉴了PostgreSQL。
节点的创建
确定了节点的结构和语法树的形状,就到了实际创建语法树的过程。通常,1条SQL语句的语法树存在多个节点,数据库中要高效的执行多条语句,就会遇到大量的节点创建问题。如果每次创建都调用new/malloc等系统函数必然会浪费大量的时,而且还会造成内存碎片。这种问题常常会出现在现实中,各个数据库都有自己不同的解决办法,但基本原理是一次性向系统申请较大的内存,然后在需要的时候自行分配。
接下来我将对比PostgreSQL,RedBase,OceanBase 3个数据库是各自如何解决这一问题的。
PostgreSQL创建语法树节点
PostgreSQL使用两个宏完成节点的创建,即makeNode和newNode.
#define makeNode(_type_) ((_type_ *) newNode(sizeof(_type_),T_##_type_))
/* 针对gcc版本的newNode */#define newNode(size, tag) \
({ Node *_result; \
AssertMacro((size) >= sizeof(Node));/* 检测申请的内存大小,>>=sizeof(Node) */ \
_result = (Node *) palloc0fast(size); /* 申请内存 */ \
_result->type = (tag); /*设置TypeTag */ \
_result; /*返回值*/\
})
#define palloc0fast(sz) \
( MemSetTest(0, sz) \
MemoryContextAllocZeroAligned(CurrentMemoryContext, sz) : \
MemoryContextAllocZero(CurrentMemoryContext, sz) )
注意:要避免直接使用newNode来创建节点,因为节点的大小在不同的环境下可能是不同的。使用makeNode即可,如:
Stmt *s = makeNode(Stmt);
我们知道,PostgreSQL中继承自NODE*的结构体们各自大小不一,都大于等于NODE 的大小。newNode中使用palloc0fast申请内存,它仍然是个宏,最终调用的是PostgreSQL中的MemoryContextAllocZeroAligned或者MemoryContextAllocZero函数,这两个函数来自PostgreSQL中内存管理模块--内存上下文,该模块能够很好的应对内存碎片,向数据库进程提供缓冲区管理。
RedBase创建语法树节点
RedBase没有提供向PostgreSQL一样复杂的内存管理机制,而是采用简单的方法来解决频繁创建节点的问题。因为RedBase中语法树的节点都是NODE类型的,所有RedBase使用一个NODE的数组来作为缓冲区,默认最大值为100,超过这个值就会创建出错,并最终退出程序。如果你的SQL语句需要的节点超过100,需要在编译之前修改最大节点数目MAXNODE以适应需求。
#define MAXNODE 100 //最大节点数目
static NODE nodepool[MAXNODE];
static int nodeptr = 0;
/*
* newnode: allocates a new node of the specified kind and returns a pointer
* to it on success. Returns NULL on error.
*/
NODE *newnode(NODEKIND kind)
{
NODE *n;
/* if we've used up all of the nodes then error */
if(nodeptr == MAXNODE){
fprintf(stderr, "out of memory\n");
exit(1);
}
/* get the next node */
n = nodepool + nodeptr;
++nodeptr;
/* initialize the `kind' field */
n -> kind = kind;
return n;
}
OceanBase创建语法树节点
OceanBase创建语法树节点的方式与PostgreSQL类似,上层调用,底层有负责内存池管理的模块来负责真正的创建,不过不是内存上下文,而是一个类ObStringBuf.
OceanBase的new_node函数申请内存时调用的是parse_malloc函数,该函数专用于语法树结构体的创建,从obStringBuf中获取内存,直到obStringBuf线程退出时才会真正释放这些内存。
大型的数据库都会有专门的内存管理模块来负责语法程序运行过程中的内存申请和释放,小型的数据库则结合自身特点,量身定制轻量的内存管理机制。目的都只有一个:降低频繁创建和销毁语法树节点的开销。
总结
SQL的执行包括了解析语法树,制