淘宝数据库OceanBase SQL编译器部分 源代码阅读--生成逻辑计划

SQL编译解析三部曲分为:构建语法树。生成逻辑计划。指定物理运行计划。

第一步骤,在我的上一篇博客淘宝数据库OceanBase SQL编译器部分 源代码阅读--解析SQL语法树里做了介绍,这篇博客主要研究第二步。生成逻辑计划。

一、 什么是逻辑计划?

我们已经知道,语法树就是一个树状的结构组织,每一个节点代表一种类型的语法含义。如

update student set sex="M" where name
="小明";


这条SQL的语法树形状为:

|Update Stmt

|----Table:student

|----TargeList:

|--------sex = "M"

|----Qualifications:

|--------name="小明"

可是只语法树并不能知道数据库中是否存在student这张表,这张表是否有sex,name这两个字段,我们是否有权限改动这条记录等。

语法树只能推断这条SQL的写法是否正确,不能确定这条SQL能否够运行。

逻辑计划须要明白SQL语句中所涉及到的表,字段,表达式等是否有效。

这个的逻辑计划与在《数据库系统实现》等书中描写叙述的逻辑查询计划不同。逻辑查询计划将SQL语句直接转为可运算的关系表达式。在OceanBase中。逻辑计划则仅仅是查找或生成涉及到的表的ID,涉及字段的ID。涉及表达式的ID等,逻辑计划是不可运算的。

二、逻辑计划包括哪些内容?

简单来说,逻辑计划要弄清楚。这条SQL能够分解为几条stmt,每条stmt包括了哪些表,字段和表达式。在此基础上,假设是insert的Stmt,要加上设置哪些值;假设是update的stmt,要加上须要更新的列和相应的值,等等。

在一个逻辑计划中。每个查询有一个唯一标识qid,每一张表有一个唯一的标识tid,每个列有一个唯一的标识cid,每个表达式有一个唯一的标识eid

来看OceanBase中的逻辑计划的结构(省略无关方法和变量).

class ObLogicalPlan
{
//...
oceanbase::common::ObVector<ObStmt*> stmts_; //存储该逻辑计划的全部stmt
oceanbase::common::ObVector<ObSqlRawExpr*> exprs_; //逻辑计划的全部表达式
oceanbase::common::ObVector<ObRawExpr*> raw_exprs_store_;//存储逻辑计划的全部表达式
uint64_t new_gen_tid_;//用于生成新的tid
uint64_t new_gen_cid_;//用于生成新的cid
uint64_t new_gen_qid_;//用于生成新的qid
uint64_t new_gen_eid_;//用于生成新的eid
};

oceanbase::common::ObVector是OceanBase中自己实现的泛型容器之中的一个,作用与STL的vector同样。

stmts_存储该逻辑计划的全部stmt;

raw_exprs_store_只用于存储表达式。exprs_则引用raw_exprs_store_中的内容。

new_gen_tid_等4个变量是用来生成新的标识时使用。一个逻辑是能够用多个tid,多个cid,多个eid。多个qid的。

这些标识分布于存储的stmt和表达式中

注:stmt实在不知道中文该怎么称呼,就不改中文名了。

2.1 逻辑计划中表的定义

struct TableItem
{
uint64_t table_id_;
common::ObString table_name_;
common::ObString alias_name_;
TableType type_; uint64_t ref_id_;
};

table_id_唯一标识一个关系表,其类型分为基本表,引用表和子查询关系。

对同一个实体表,ref_id_table_id_同样;
假设是一个引用别名的表。则table_id_是新生成的,ref_id_与这个表真正的table_id_同样;假设是一个子查询,则table_id_是新生成的,ref_id_是对子查询的引用。

对同一个实体表,它在全部线程使用的table_id_都是同样的。假设是生成的标识。则仅在该线程会话期间是唯一的。

2.2 逻辑计划中列的定义

struct ColumnItem
{
uint64_t column_id_;
common::ObString column_name_;
uint64_t table_id_;
uint64_t query_id_; bool is_name_unique_;
bool is_group_based_; common::ObObjType data_type_;
};

column_id_唯一标识一个列,table_id_query_id_为该列所属的关系表和stmt。is_name_unique_仅用在解析逻辑计划期间。标记该列的名称是否在全部表的字段中都是唯一的。

is_group_based_标记该列是否用于分组。

data_type_标识该列的数据类型。

2.3 逻辑计划中的表达式的定义

逻辑计划的中表达式有多种类型。其基类为ObRawExpr.包含两个成员变量,type_表示表达式的类型,result_type_表示表达式值的类型。

class ObRawExpr
{
//省略其它方法
private:
ObItemType type_;
common::ObObjType result_type_;
}

表达式分为常量表达式, 一元引用表达式,二元引用表达式,一元操作符表达式。二元操作符表达式,三元操作符表达式,多元操作符表达式。case操作符表达式,聚集函数表达式,系统函数表达式,SQL原生表达式等。

继承关系例如以下。

namespace sql
{
//原生表达式基类
class ObRawExpr
//常量表达式
class ObConstRawExpr : public ObRawExpr
//一元引用表达式
class ObUnaryRefRawExpr : public ObRawExpr
//二元引用表达式
class ObBinaryRefRawExpr : public ObRawExpr
//一元操作符表达式
class ObUnaryOpRawExpr : public ObRawExpr
//二元操作符表达式
class ObBinaryOpRawExpr : public ObRawExpr
//三元操作符表达式
class ObTripleOpRawExpr : public ObRawExpr
//多元操作符表达式
class ObMultiOpRawExpr : public ObRawExpr
//case操作符表达式
class ObCaseOpRawExpr : public ObRawExpr
//聚集函数表达式
class ObAggFunRawExpr : public ObRawExpr
//系统函数表达式
class ObSysFunRawExpr : public ObRawExpr
//SQL原生表达式
class ObSqlRawExpr : public ObRawExpr
}; class ObRawExpr
{ };

在ObLogicalPlan中。存储使用的是vector<ObRawExpr
*>
,使用时转为vector<ObSqlRawExpr *>.

2.4 逻辑计划中的Stmt的定义

Stmt表示一个单独的查询所包括的内容,一个逻辑计划能够包括多个Stmt.

 class ObStmt
{
/*省略部分内容...*/
protected:
common::ObVector<TableItem> table_items_;
common::ObVector<ColumnItem> column_items_; private:
StmtType type_;
uint64_t query_id_;
//uint64_t where_expr_id_;
common::ObVector<uint64_t> where_expr_ids_; };

Stmt包含了一个查询全部的表table_items_,列column_items_,表达式where_expr_ids_和一个唯一的查询标识query_id_。注意这里存储的仅仅有表达式的id,而不是表达式的实际内容。

从上述的定义总结来看,一个逻辑计划拥有多条查询实例Stmt和多个表达式,一个查询实例Stmt包括了多个表和多个列及所需表达式的引用。

表。列。表达式。查询实例都有唯一的标识符进行标记。

ObLogicalPlan

----ObStmt : 1...n

--------TableItem : 0...n

--------ColnumItem : 0...n

--------expr_id_ref : 0...n

----ObRawExpr : 0...n

三、 怎样制定逻辑计划?

3.1 reslove系列解析函数

制定逻辑计划的源代码在build_plan.h和build_plan.cpp中。在OceanBase0.4中,则添加了dml_build_plan.h和dml_build_plan.cpp。制定逻辑对外提供的接口仅仅有两个,解析函数resolove和销毁函数destroy_plan,其它的为自用,能够浏览下其函数声明及用途。主要的结构就是这样,由于眼下OceanBase中支持的SQL语句不多。对应的解析函数也比較少。另一些没有完毕,能够想见未来还会加入很多其它的函数。

//解析多重查询
int resolve_multi_stmt(ResultPlan* result_plan, ParseNode* node)
//解析独立表达式
int resolve_independ_expr()
//解析and表达式
int resolve_and_exprs()
//解析表达式
int resolve_expr()
//解析聚集函数
int resolve_agg_func()
//解析join表连接
int resolve_joined_table()
//解析表
int resolve_table()
//解析from子句
int resolve_from_clause()
//解析列
int resolve_table_columns()
//解析*
int resolve_star()
//解析select的投影列表
int resolve_select_clause()
//解析where子句
int resolve_where_clause()
//解析group by子句
int resolve_group_clause()
//解析having子句
int resolve_having_clause()
//解析order子句
int resolve_order_clause()
//解析limit子句
int resolve_limit_clause()
//解析select查询
int resolve_select_stmt()
//解析delete查询
int resolve_delete_stmt()
//解析insert的插入列
int resolve_insert_columns()
//解析intsert查询的插入值
int resolve_insert_values()
//解析insert查询
int resolve_insert_stmt()
//解析update查询
int resolve_update_stmt()
//解析函数。对外提供
int resolve(ResultPlan* result_plan, ParseNode* node)
//销毁函数。对外提供
extern void destroy_plan(ResultPlan* result_plan)

resolve函数依据语法树node的类型调用不同的查询解析实例。

下面是部分代码摘抄:

int resolve(ResultPlan* result_plan, ParseNode* node)
{
/*...*/ uint64_t query_id = OB_INVALID_ID;
if (ret == OB_SUCCESS && node != NULL)
{
switch (node->type_)
{
case T_STMT_LIST:
{
ret = resolve_multi_stmt(result_plan, node);
break;
}
case T_SELECT:
{
ret = resolve_select_stmt(result_plan, node, query_id);
break;
}
case T_DELETE:
{
ret = resolve_delete_stmt(result_plan, node, query_id);
break;
}
case T_INSERT:
{
ret = resolve_insert_stmt(result_plan, node, query_id);
break;
}
case T_UPDATE:
{
ret = resolve_update_stmt(result_plan, node, query_id);
break;
}
default:
ret = OB_ERROR;
break;
};
}
return ret;
}
int resolve_update_stmt(ResultPlan* result_plan, ParseNode* node, uint64_t& query_id)
{
int& ret = result_plan->err_stat_.err_code_ = OB_SUCCESS;
uint64_t table_id = OB_INVALID_ID; query_id = OB_INVALID_ID; ObLogicalPlan* logical_plan logical_plan = new(logical_plan) ObLogicalPlan(name_pool); result_plan->plan_tree_ = logical_plan; update_stmt = new(update_stmt) ObUpdateStmt(name_pool); query_id = logical_plan->generate_query_id();
//为update_stmt设置新的标识qid
update_stmt->set_query_id(query_id); logical_plan->add_query(update_stmt); ParseNode* table_node = node->children_[0];
//解析表
ret = resolve_table(result_plan, update_stmt, table_node, table_id); update_stmt->set_update_table(table_id); ParseNode* assign_list = node->children_[1]; uint64_t ref_id;
ColumnItem *column_item = NULL;
//解析要更新的列表,如:update student set sex="M",grade="2" where name = "xiaoming";
for (int32_t i = 0; ret == OB_SUCCESS && i < assign_list->num_child_; i++)
{
ParseNode* assgin_node = assign_list->children_[i]; /* resolve target column */
ParseNode* column_node = assgin_node->children_[0]; ObString column_name;
column_name.assign_ptr(
(char*)(column_node->str_value_),
static_cast<int32_t>(strlen(column_node->str_value_))
);
//1 依据列名获取列
column_item = update_stmt->get_column_item(NULL, column_name);
//2 解析列到vector<ColumnItem *>
ret = update_stmt->add_column_item(*result_plan, column_name, NULL, &column_item);
//3 添加列引用到update_stmt
ret = update_stmt->add_update_column(column_item->column_id_); /* resolve new value expression */
//4 解析值表达式
ParseNode* expr = assgin_node->children_[1];
ret = resolve_independ_expr(result_plan, update_stmt, expr, ref_id, T_UPDATE_LIMIT);
//5 加入值表达式引用到update_stmt
ret = update_stmt->add_update_expr(ref_id)
}
//解析where子句
ret = resolve_where_clause(result_plan, update_stmt, node->children_[2]); return ret;
}

我们仍旧以update语句为例。

上面是依据源代码整理的逻辑,不是源代码。主要是为了理清思路。

  1. 首先是创建一个新的查询update_stmt,并为其生成一个独立的查询标识qid
  2. 解析语句中的表。并将表的标识tid加入到update_stmt的引用列表
  3. 利用for循环逐个解析要更新的列-值对:

    (1). 依据列名获取列;

    (2). 将该列存储到update_stmtvector<ColumnItem
    *>
    中,并将列引用id加入到update_stmt的更新列列表ObArray<uint64_t>
    update_columns_
    中;

    (3). 解析值表达式;

    (4). 将值表达式引用id加入到更新值列表ObArray<uint64_t>
    update_exprs_
    中去;
  4. 解析where子句.

3.2 怎样解析表和列?

通过上面我们知道,逻辑计划的解析的一个重要内容就是要确定查询stmt,表,列,表达式的标识.查询和表达式的标识id都能够在解析的时候生成。由于这两项不是线程共同拥有的,可是表和列是持久的数据,能够跨线程使用相同的id。这些表和列的信息由谁来管理?

3.2.1 使用Schema

追根溯源,你会发现实体表和列的id是在ob_schema.cpp中获取的。

什么是schema?schema就是数据库对象的一个集合

网上有一个非常形象的比喻,我略微做了点修改:

posted on
2017-06-14 13:56 
lxjshuju 
阅读(...) 
评论(...) 
编辑 
收藏

淘宝数据库OceanBase SQL编译器部分 源代码阅读--生成逻辑计划的更多相关文章

  1. 淘宝数据库OceanBase SQL编译器部分 源代码阅读--生成物理查询计划

    SQL编译解析三部曲分为:构建语法树,制定逻辑计划,生成物理运行计划. 前两个步骤请參见我的博客<<淘宝数据库OceanBase SQL编译器部分 源代码阅读--解析SQL语法树>& ...

  2. 淘宝数据库OceanBase SQL编译器部分 源代码阅读--Schema模式

    淘宝数据库OceanBase SQL编译器部分 源代码阅读--Schema模式 什么是Database,什么是Schema,什么是Table,什么是列,什么是行,什么是User?我们能够能够把Data ...

  3. 淘宝数据库OceanBase SQL编译器部分 源代码阅读--解析SQL语法树

    OceanBase是阿里巴巴集团自主研发的可扩展的关系型数据库,实现了跨行跨表的事务,支持数千亿条记录.数百TB数据上的SQL操作. 在阿里巴巴集团下,OceanBase数据库支持了多个重要业务的数据 ...

  4. 淘宝数据库OceanBase SQL编译器部分 源码阅读--生成逻辑计划

    body, td { font-family: tahoma; font-size: 10pt; } 淘宝数据库OceanBase SQL编译器部分 源码阅读--生成逻辑计划 SQL编译解析三部曲分为 ...

  5. 《淘宝数据库OceanBase SQL编译器部分 源码阅读--解析SQL语法树》

    淘宝数据库OceanBase SQL编译器部分 源码阅读--解析SQL语法树   曾经的学渣 2014-06-05 18:38:00 浏览1455 云数据库Oceanbase   OceanBase是 ...

  6. 淘宝数据库OceanBase SQL编译器部分 源码阅读--生成物理查询计划

    淘宝数据库OceanBase SQL编译器部分 源码阅读--生成物理查询计划 SQL编译解析三部曲分为:构建语法树,制定逻辑计划,生成物理执行计划.前两个步骤请参见我的博客<<淘宝数据库O ...

  7. 淘宝:OceanBase分布式系统负载均衡案例分享

    Heroku因"随机调度+Rails单线程处理导致延迟增加的负载均衡失败"的案例之后,我们在思考:在负载均衡测试时发现问题并妥善解决的成功经验有没有?于是,挖掘出"淘宝在 ...

  8. 利用Selenium+java实现淘宝自动结算购物车商品(附源代码)

    转载请声明原文地址! 本次的主题是利用selenium+java实现结算购买购物车中的商品. 话不多说,本次首先要注意的是谷歌浏览器的版本,浏览器使用的驱动版本,selenium的jar包版本.   ...

  9. Java 实现 淘宝秒杀 聚划算 自己主动提醒 源代码

    说明 本实例可以监控聚划算的抢购button,在聚划算整点聚的时间到达时自己主动弹开页面(URL自定义). 能够自己定义监控持续分钟数,同一时候还能够通过多线程加快刷新速度. 源代码 package ...

随机推荐

  1. 关于hexo博客自定义域名后gitment评论系统登陆出现redirect error返回主页的解决办法

    title: 关于hexo博客自定义域名后gitment评论系统登陆出现redirect error返回主页的解决办法 toc: false date: 2018-04-16 22:57:50 cat ...

  2. NOIP2013T1 转圈游戏 快速幂

    描述 n 个小伙伴(编号从 0 到 n-1)围坐一圈玩游戏.按照顺时针方向给 n 个位置编号,从0 到 n-1.最初,第 0 号小伙伴在第 0 号位置,第 1 号小伙伴在第 1 号位置, --, 依此 ...

  3. Oracle性能优化——总体介绍

    最近参加Oracle的培训,对Oracle有了更加深入的认识,在此做个学习总结. 1.Oracle数据库调优不能仅指望修改几项数据库参数就能有明显效果,问题更多出在应用方面,教育开发者正确地使用数据库 ...

  4. ModelState对象

    1.在控制器中判断Model验证结果

  5. 让break跳出外层循环的方法

    demo //在里层循环里面,想办法让外层循环的条件不成立,就可以控制外层循环结束. for(var i = 0 ; i < 10; i++){ alert(i) for(var j = 0 ; ...

  6. @section Scripts{}的使用

    MVC视图中,Javascripts代码被放于下面的Razor代码中(@section Scripts{}). 好处:在视图进行JavaScript编程时,是一个很好的实践,在共享视图(_Layout ...

  7. IE兼容opacity

    filter:alpha(opacity=80); opacity: 0.57; /* Firefox, Safari(WebKit), Opera) filter: "alpha(opac ...

  8. jquery获取元素内容-text()和val()

    不传参数的text()方法在获取文本内容时,会把子元素的文本也获取过来(会删除 HTML 标记),例子: <!doctype html> <html> <head> ...

  9. Css进阶练习(实现抽屉网样式布局)

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. BZOJ 4367 [IOI2014]holiday (决策单调DP+主席树+分治)

    题目大意:略 题目传送门 神题,不写长题解简直是浪费了这道题 贪心 考虑从0节点出发的情况,显然一直往前走不回头才是最优策略 如果起点是在中间某个节点$s$,容易想到,如果既要游览$s$左边的某些景点 ...