zoukankan      html  css  js  c++  java
  • MySQL Item 源码阅读笔记

    MySQL Item 源码阅读笔记

    Based on MySQL8.0 community version

    Outline

    1. Item的内容与作用
    2. Item的构建
    3. 几种典型Item的介绍
    4. Item表达式求值与相关优化的实现
    5. Item与下推优化

    1. Item的内容与作用

    一个前人画的Item继承关系图,应该是基于MySQL 5.x:http://www.orczhou.com/wp-content/uploads/2012/11/classItem__inherit__graph.png

    Item(继承自Parse_tree_node)是用于表示条件表达式查询的结点(包括sub select),在其他AP引擎中条件表达式一般会表达成多叉结点树结构,Item组织关系逻辑上也是棵树。

    一般条件表达式结点的分类是:

    • 常量节点/值节点(对应Item_base_constant):存储常量值

    • 字段节点/列节点(对应Item_field):存储列字段的相关元信息

    • 函数计算节点(对应Item_func):分为系统函数和UDF。系统函数指 +-*/ =><等系统提供的基本函数型操作,也包含一些常用的函数,比如一些数学函数、加密函数等。有的其他AP引擎实现会将大部分的System func基于UDF实现。

      • 逻辑计算节点(对应Item_cond):主要是and、or、not等。这类函数可以看作是输入值为1个(not)或2个bool参数,返回值为bool的特殊函数。因此实现时也会基于函数计算节点去实现,但在表达式优化和计算时会另外看待。MySQL not实现在Item_func_not中。
    • 聚合函数计算(对应Item_sum):分为系统聚合函数和UDF(有的也叫UDAF)。系统聚合函数包括sum、count、avg、max、min等。

    与大部分表达式节点树不同的是,Item对象除了节点表示之外还承载了计算的功能。以下为Item的主要作用:

    • 表达式节点表示。
      • Item_base_constant
      • Item_field
      • Item_func
    • 计算。每个Item对象都有val_xxx方法,尤其是val_int和val_str这两个方法MySQL内置Item类型都支持调用。以val_int举例,调用其可以得到以该Item为根节点的子树的求值。
    • 遍历(调用入口为walk方法)。Item里定义了很多只属于其子类的Item_processor方法,具体的walk实现也是在相应子类中,除了Item_subselect,其他的walk实现都差不多。
    • Transform&Compile(对应transform和compile方法):Transform表示对Item tree的转换,可能会添加0或多个新的Item节点;Compile则是会在当前节点transform之前做一次该节点子树的analyze,。

    2. Item的构建

    MySQL会通过yacc解析将条件表达式解析成一颗Item树(暂称为解析树)。解析树里会有一部分是PTI_开头的Item,PTI_Item都是继承自Parse_tree_item(也是Item的子类),是一种解析过程中过渡的Item(注释里认为这是一种placeholder)。在contextualize阶段时,会对这些PTI_item进行itemize,将它们从解析树节点转化成真正意义的表达式树节点。

    需注意:

    1. 部分非PTI_Item (比如非date的常量类的等比较简单的节点)会在yacc解析时直接构造。PTI_Item可以认为是一种过渡,只是因为实现方式问题而存在,并非是HighLevel意义上一定要存在的概念。
    2. 此时解析出来的表达式树未必是最终的完整版,后面经过transform/compile等操作有可能会改变树的结构。
    3. 不同的Item的构造时机不一样,需case by case看,有的是在yacc解析时直接构造,有的是在itemize的时候构造。
    常量节点
    • 非时间类型的常量,会在yacc解析时直接构造相应的Item
    • 时间类型的常量会先解析成PTI_temporal_literal,PTI_temporal_literal::itemize中会调用create_temporal_literal来转换成对应的时间类型的Item。
    TODO: 字段节点
    • Select 函数内的field,i.e. SELECT sum(l_extendedprice)
    • Where 的field, i.e.WHERE l_returnflag='A'
    • Where 函数内的field, i.e. WHERE abs(l_extendedprice) > 2

    // TODO: refix_fields是干啥的?

    3.几种典型Item的介绍

    常量节点:Item_num

    Item_num是表示数值型的常量,类里存储的就是对应数值常量值value,int/bigint统一存成longlong,float/double统一存成double,decimal类型自己有一个Item_decimal实现。

    数值型的实现简单可表示成如下:

    class Item_xx : public Item_num {  // xx for int/uint/float/decimal...
      NUM_TYPE value;
      
      int val_int() {
        // return int rep of value;
      }
      
      double val_real() {
        // return  double rep of value;
      }
    };
    

    常量节点:Item_string

    存储字符串常量值,类型默认为VARCHAR。varchar变量关注str_value、collation、max_length。

    • str_value存储字符串值
    • collation存储字符集编码
    • max_length存储的是根据编码实际encode后的字符串最大长度 (VARCHAR是变长的)

    其中val_int的实现是my_strtoll10,可以理解为是一个string到longlong的hash实现。

    常量节点:Item_date_literal

    时间类的Item实现都在item_timefunc.h/cc,时间相关的函数在MySQL里一般都包含temporal的命名。

    Item_date_literal继承自Item_date_func,是因为MySQL的SQL中表示DATE常量是用DATE '2019-01-01'这种函数形式实现的。内部存储是一个MYSQL_TIME_cache对象,里面的MYSQL_TIME会以struct形式存储年月日时分秒的信息,同时还支持微秒us (microsecond)。需注意内部时间有多种表示,以DATE举例:

    • struct MYSQL_TIME,直观的结构体表示
    • val_int() ,MYSQL_TIME_cache::time_packed ,将年月日时分秒表示成整型形式,比如2019-01-01表示成整型20190101 。(私以为这个还不如时间戳统一)
    • string representation "2019-01-01"
    • 存储时encode成3字节的存储格式的int表示

    DATE/DATETIME/TIME的实现和上述相似。

    Cond节点:Item_cond_and

    Item_cond_and继承自Item_cond,本身没有什么新的方法或属性。唯一不同的是它的children是存在一个List<Item> list成员变量里,而并非使用Item的arguments来存储。

    Item_cond_or类似不再赘述。

    字段节点:Item_field

    字段节点最主要的成员变量如下:

    /**
        Table containing this resolved field. This is required e.g for calculation
        of table map. Notice that for the following types of "tables",
        no TABLE_LIST object is assigned and hence table_ref is NULL:
         - Temporary tables assigned by join optimizer for sorting and aggregation.
         - Stored procedure dummy tables.
        For fields referencing such tables, table number is always 0, and other
        uses of table_ref is not needed.
      */
      TABLE_LIST *table_ref;
      /// Source field 
      Field *field;
      /**
        Item's original field. Used to compare fields in Item_field::eq() in order
        to get proper result when field is transformed by tmp table.
      */
      Field *orig_field;
      /// Result field
      Field *result_field;
      Item_equal *item_equal;
    
    • 在一些处理逻辑中,table_ref表示该Field所属的table
    • field存储实际的字段值,每次read record后会将record store到相应的field里以便表达式计算。table scan里这一步是在handler::position()方法里由handler自己实现的,从uchar* record提取字段设置到table里。Item_field里的field和table的对应field 指向同一个Field对象。
    • orig_field、result_field和item_equal未知

    聚合节点:Item_sum

    Item_sum不代表sum函数(sum函数实现是Item_sum_sum),Item_sum是所有agg函数的父类(叫Item_agg可能更合适)。Item_sum都会有一组接口:

    virtual void clear() = 0;
    virtual bool add() = 0;
    virtual bool setup(THD *) { return false; }
    // 以及 val_xxx 接口
    

    可以把一个agg看成一组操作的组合:setup + N * add + val_xxx ,即初始化、流式操作或计算数据、合并计算。调用这组接口的是Aggregator类,Aggregator有两个子类实现 simple和distinct,simple什么都不做直接传递调用;distinct会借助去重树或临时表去做distinct操作。

    Item_sum另外一类重要的变量和函数是关于window的,这个另外再提。

    子查询节点:Item_subselect

    待看完子查询相关再写

    4.Item表达式求值

    Item的求值的核心方法就是val_xxx函数,统一的接口可以从val_int看进去,因为所有Item都会有个val_int的实现(内部可能会调用它实际的val_xxx类型的实现,然后转为int表示或hash值)。常量节点求值逻辑上面有部分介绍,函数节点就是函数的计算逻辑。

    表达式计算调用在evaluate_join_record中,仅需要短短一句condition->val_int()来判断是否被筛选掉。

    // static enum_nested_loop_state evaluate_join_record(JOIN *join, QEP_TAB *const qep_tab);
    
    Item *condition = qep_tab->condition();
    bool found = true;
    
    if (condition) {
        found = condition->val_int();
    
        if (join->thd->killed) {
          join->thd->send_kill_message();
          DBUG_RETURN(NESTED_LOOP_KILLED);
        }
    
        /* check for errors evaluating the condition */
        if (join->thd->is_error()) DBUG_RETURN(NESTED_LOOP_ERROR);
      }
    

    常量表达式会将节点const_for_execution设为true。但是除了eval_const_cond用于判断部分bool值表达式的常量计算外,比如 col > 1+2这种并未优化成 col>3

    5.Item与谓语下推优化

    谓语下推核心是handler的cond_push函数(默认未实现)或idx_cond_push函数。

    5.x版的cond_push会在两个地方被调用,一个是优化器里,一个是records.cc里(for execution)。这里SELECT会触发两次的cond_push,该问题已在社区被汇报成issue。

    8.0版的优化器里的cond_push被保留,records.cc里的去掉,相应的移到了sql_update.cc/sql_delete.cc里,避免了SELECT触发两次cond_push的bug。(RDS这边的封了个PushDownCondition,仍未解这个问题)。

    // JOIN::optimize()
    if (thd->optimizer_switch_flag(
                      OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN) &&
                  first_inner == NO_PLAN_IDX) {
                Item *push_cond = make_cond_for_table(
                    thd, tmp, tab->table_ref->map(), tab->table_ref->map(), 0);
                if (push_cond) {
                  /* Push condition to handler */
                  if (!tab->table()->file->cond_push(push_cond))
                    tab->table()->file->pushed_cond = push_cond;
                }
              }
    
    
    

    make_cond_for_table已经保证抽取出来的push_cond是针对单表的condition了,handler相应实现拿到Item可以遍历或转化成自己想要的结构处理,这部分不在此赘述。

    有个未确认的问题。实际的下推接口是一对接口 cond_push & cond_pop,而idx_cond_push不存在pop接口。按照ndb的实现,cond_push的是一个栈push操作,不知道为啥condition会构成一个栈结构存在。事实发现似乎不理会cond_pop,就当每个查询每个表只会调用一次cond_push也是没问题的。

  • 相关阅读:
    6-4.粗体标签
    [Unity3D] 如何实现点击按钮退出游戏
    [Unity3D] 载入游戏地图时背景图片随机切换 & 数字百分比进度条
    [Unity3D] 鼠标点击图片移动效果
    [3DMAX]如何将骨骼与模型绑定在一起(蒙皮) & 如何实现自动化人物模型蒙皮
    [Unity 3D]用鼠标滚轮实现镜头放大和缩放,并添加距离限制
    [Unity3D] 如何实现围绕旋转
    [Unity3D] 如何实现注视旋转
    Css 图片自适应
    Scss 定义内层class的简单写法
  • 原文地址:https://www.cnblogs.com/lhfcws/p/14879891.html
Copyright © 2011-2022 走看看