问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

淘宝研发的 OceanBase 相比其他开源的 noSQL 数据库有什么独特的优点...

发布网友 发布时间:2022-04-29 16:16

我来回答

2个回答

懂视网 时间:2022-05-04 23:26

一、 什么是逻辑计划?

我们已经知道,语法树就是一个树状的结构组织,每个节点代表一种类型的语法含义。如
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 stmts_; //存储该逻辑计划的所有stmt
 oceanbase::common::ObVector exprs_; //逻辑计划的所有表达式
 oceanbase::common::ObVector 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,使用时转为vector.

2.4 逻辑计划中的Stmt的定义

Stmt表示一个单独的查询所包含的内容,一个逻辑计划可以包含多个Stmt.

 class ObStmt
 {
 /*省略部分内容...*/
 protected:
 common::ObVector table_items_;
 common::ObVector column_items_;

 private:
 StmtType type_;
 uint64_t query_id_;
 //uint64_t where_expr_id_;
 common::ObVector 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(strlen(column_node->str_value_))
  );
 //1 根据列名获取列
 column_item = update_stmt->get_column_item(NULL, column_name);
 //2 解析列到vector
 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中,并将列引用id添加到update_stmt的更新列列表ObArray update_columns_中;
    (3). 解析值表达式;
    (4). 将值表达式引用id添加到更新值列表ObArray update_exprs_中去;
  4. 解析where子句.

3.2 如何解析表和列?

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

3.2.1 使用Schema

追根溯源,你会发现实体表和列的id是在ob_schema.cpp中获取的。什么是schema?schema就是数据库对象的一个集合。网上有一个很形象的比喻,我稍微做了点改动:

什么是Database,什么是Schema,什么是Table,什么是列,什么是行,什么是User?我们可以可以把Database看作是一个大仓库,仓库分了很多很多的房间,Schema就是其中的房间,一个Schema代表一个房间,Table可以看作是每个Schema中的柜子,行和列就是柜子中的格子。User就是房间的主人。

OceanBase要求schema强类型约束,也就是要预先定义好schema。这样可以方便的进行各种online ddl操作。

OceanBase的表和列信息存储在Schema的一个hash_map中。关于Schema 以后再深入了解。

四、总结

制定逻辑计划,最关键的是要理解逻辑计划的设计目标,其中最主要的内容就是确定逻辑计划中使用到的查询Stmt,表,列,表达式,并为它们生成或设置唯一标识,确保在同一个逻辑计划中是不相同的。制定逻辑计划的函数只要是reslove系列函数。而表和列的信息存储在Schema中一个hash_map中。


欢迎光临我的网站----我的博客园----我的CSDN。
如果阅读本文过程中有任何问题,请联系作者

热心网友 时间:2022-05-04 20:34

可以看做是单机内存数据库加上db link 查询远端数据库,另外,同意楼上的,"明显的单机瓶颈的UpdateServer",这机器的配置得上去
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
瑞安皇都大厦几个电梯 办签证和护照的具体步骤和一共的费用!我需要详细一点的答案. 办护照流程 办护照的流程是什么 哪位推荐几部韩剧啊!!要像豪杰春香,魔女幼熙这样的,剧情要感人,最好是... 小阴唇内应该是光滑的吗,阴道口痒为什么 阴道口有像草莓样正常吗 任氏族谱字辈:世秉忠贞 永达明玉起 国正天元律迎祖大继昌 河南夏氏家谱辈分我是永城市酂城镇夏氏夏世界具家谱记载洪武二年始祖良... 夏侯姓字辈排行 夏氏辈行,我爷爷明字辈,我父亲应字辈,我是崇字辈,后面是什么辈? 淘宝怎样上传db格式数据库 淘宝用什么语言写的?php还是java? 淘宝交易系统用什么语言和架构去设计,又有多少行程式 opporeno7前置照相怎么调节远近镜头 淘宝网是用什么编程语言开发的? 在PS下如何把照片背景做成透明色? 淘宝服务器 什么系统 小区一楼商铺伸出去的屋顶漏水,是否应该用楼上住户的维修基金? 淘宝的数据库怎么搭建? 顶楼漏水,维修基金可以紧急启用吗 如何用美图秀秀把照片的背景变透明 请问淘宝里的数据库指的是什么? 华为手机拍照,怎么调整人物远近 房屋自带给水管漏水可以动用维修基金吗? 怎样把照片底色换成透明的 淘宝 用的数据库 是 mysql 还是 sql server? 淘宝有这么多技术人员 全部用mysql数据库不是好好的吗?为什么还要花那 ... 淘宝网用的数据库是Mysql吗 淘宝用什么数据库 暴雨后房子漏水怎么修?维修基金可以用吗 华为手机的照相机怎么调距离 淘宝服务平台里 数云数据赢家和客道哪个更好、更适合淘宝卖家? 苹果4s拍照怎么调远近 淘宝网,支付宝目前用到了哪些java开源框架以及NoSQL数据库 华为手机的照相机怎么调距离? 淘宝网为什么是用PHP程序开发 如何看出一个女孩子是否喜欢你? 怎么判断一个女生是否喜欢你。 怎样判断一个女孩子喜欢你 怎样测试一个女孩子是否喜欢你? 怎么判断一个女孩子是否喜欢你 wps怎么填充空白单元格 WPS非重复性填充功能,在哪里? wps的数字填充设置在哪 嘉荣购物卡咋使用? 东莞嘉荣超市会员卡积分有哪些用途? 谁知道 嘉荣/SPAR购物卡 除了在嘉荣能购物 还有啥用? 嘉荣超市会员卡通用吗? 广东嘉荣超市购物卡怎样激活 嘉荣购物广场的购物卡