淘宝数据库OceanBaseSQL编译器部分源码阅读--解析SQL语法树(一)

2014-11-24 17:02:50 · 作者: · 浏览: 1
OceanBase是阿里巴巴集团自主研发的可扩展的关系型数据库,实现了跨行跨表的事务,支持数千亿条记录、数百TB数据上的SQL操作。在阿里巴巴集团下,OceanBase数据库支持了多个重要业务的数据存储,包括收藏夹、直通车报表、天猫评价等。截止到2013年4月份,OceanBase线上业务的数据量已经超过一千亿条。
看起来挺厉害的,今天我们来研究下它的源代码。关于OceanBase的架构描述有很多文档,这篇笔记也不打算涉及这些东西,只讨论OceanBase的SQL编译部分的代码。
OceanBase是一个开源的数据库,托管在github上,点击 下载。本文讨论的 源码路径对应为:oceanbase_0.3/src/sql。最新的版本为0.4,本文讨论的代码基于0.3版本的OceanBase.目前OceanBase的能解析的SQL还比较少,包括Select,Insert,Update,Delete,Show,Explain等.
选择OceanBase 0.3 版本进行学习,基于几个原因:
OceanBase 的高质量,高可读性
OceanBase 版本低,没有历史负担
OceanBase SQL解析相对简单,更容易窥见全貌,利于理解设计开发中要解决的主要问题。
与其他数据库的SQL解析部分进行对比,深入理解问题本质

该部分主要功能包括了,SQL语句解析,逻辑计划的生成,物理操作符运算等。

入口:ObSql类

本部分的入口函数在ob_sql.h中,调用函数ObSql::direct_execute可以直接执行SQL语句,并返回结果集ObResultSet。函数stmt_prepare用于解析要预编译的SQL语句,stmt_execute则用于执行Prepare过的SQL语句。

  class ObSql
    {
      public:
        ObSql(){}
        ~ObSql(){}
        int direct_execute(const common::ObString &stmt, ObResultSet &result)
        int stmt_prepare(const common::ObString &stmt, ObStmtPrepareResult &result);
        int stmt_execute(const uint64_t stmt_id, const common::ObArray params, ObResultSet &result);
        int stmt_close(const uint64_t stmt_id);
    };

在0.4版本中,direct_execute,stmt_prepare,stmt_execute等函数都被声明为static函数,意味着调用SQL语句执行时可以直接ObSql::direct_execute可以执行SQL语句,而不必再先定义一个ObSql对象。OceanBase还有年轻,还存在不足,我们阅读源码时应该带着批判思考的精神

直接进入direct_execute函数,可以看到整个执行的过程,函数中有很多的if,else语句,主要是因为OceanBase有一个编码规范要求:一个函数只能有一个返回出口.按单出口的规范写代码会使得写代码的思路非常清晰,不容易出现内存泄露等问题,在大型项目中还是应该尽量保持函数单出口.当然,我觉得保持一个函数功能简洁、简单易懂也是非常重要的。

在你阅读源码的过程中,遇到的大部分函数都会是这个样.刨去其他干扰信息,结合注释,可以看到,SQL执行分为5个步骤:

初始化
parse_init(&parse_res)
解析SQL语法树
parse_sql(&parse_res, stmt.ptr(), static_cast(stmt.length()));
制定逻辑计划
resolve(&logical_plan, parse_res.result_tree_)
ObMultiPlan* multi_plan = static_cast(logical_plan.plan_tree_);
生成物理计划:
trans.add_logical_plans(multi_plan);
physical_plan = trans.get_physical_plan(0)
执行物理计划:
exec_plan->open()

初始化仅仅是初始化一个缓冲区,可以略过来研究后面关键的4步。

解析SQL语法树

像PostgreSQL,MySQl等一样,OceanBase采用lex和yacc系的词法和语法解析工具生成语法树。GNU下的词法和语法工具为Flex和Bison.Flex利用正则表达式识别SQL语句中的所有词语,Bison则根据类似BNF的语法规则识别SQL语义,从而构建语法树。不熟悉Flex与Bison的同学推荐阅读《FLEX与BISON》(貌似我也没找到其他类似的书籍,^_^),里面也有专门的一章讲述利用Flex与Bison实现一个简单的SQL分析器。

OceanBase的SQL语法树与PostgreSQL更为相似,但是设计上也有很多区别。

节点设计

语法树由一系列的节点串连而成。我选取较为简单的Update语句作为示例,下面是一个例句:

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

其SQL语法树可以表示为:

|--Update Stmt

|--Table:student

|--TargeList:

|--sex = "M"

|--Qualifications:

|--name="小明"

语法解析的作用就是如何用数据结构来表示上面这棵语法树。不同的数据库有不同的方案。为加强对比,我选取了PostgreSQL,RedBase与OceanBase作为参照。

PostgreSQL语法树的节点设计

PostgreSQL中每一种语法都会有一个对应的结构体,比如更新语句Update对应的结构体为UpdateStmt:

    typedef struct UpdateStmt
{
    NodeTag        type;           /* 枚举类型 */
    RangeVar   *relation;        /* 要更新的关系 */
    List       *targetList;        /* 要更新的项 */
    Node       *whereClause;    /* 更新条件 */
    List       *fromClause;        /* optional from clause for more tables */
    List       *returningList;    /* 返回的表达式列表 */
    WithClause *withClause;        /* WITH clause */
} UpdateStmt;

其中type是个枚举值,表示结构体的类型,在UpdateStmt中为T_UpdateStmt。其他字段分别对应UPdate语句的各个部分,该结构体可以支持更复杂的Update语句。

PostgreSQL中还有一个基础的结构体:

typedef struct Node
{
    NodeTag        type;
} Node;

用于语法解析的结构体都可以强制转换成Node * 类型。PostgreSQL中传递语法结构体是都会转化成Node *类型,只有在需要明确类型的时候根据type枚举值转换成需要的类型。Node *的作用有的类似于void * ,但