Based on MySQL8.0 community version
一个前人画的Item继承关系图,应该是基于MySQL 5.x:classItem__inherit__graph.png
Item(继承自Parse_tree_node)是用于表示条件表达式查询的结点(包括sub select),在其他AP引擎中条件表达式一般会表达成多叉结点树结构,Item组织关系逻辑上也是棵树。
一般条件表达式结点的分类是:
与大部分表达式节点树不同的是,Item对象除了节点表示之外还承载了计算的功能。以下为Item的主要作用:
MySQL会通过yacc解析将条件表达式解析成一颗Item树(暂称为解析树)。解析树里会有一部分是PTI_开头的Item,PTI_Item都是继承自Parse_tree_item(也是Item的子类),是一种解析过程中过渡的Item(注释里认为这是一种placeholder)。在contextualize阶段时,会对这些PTI_item进行itemize,将它们从解析树节点转化成真正意义的表达式树节点。
需注意:
// TODO: refix_fields是干啥的?
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;
}
};
存储字符串常量值,类型默认为VARCHAR。varchar变量关注str_value、collation、max_length。
其中val_int的实现是my_strtoll10,可以理解为是一个string到longlong的hash实现。
时间类的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举例:
DATE/DATETIME/TIME的实现和上述相似。
Item_cond_and继承自Item_cond,本身没有什么新的方法或属性。唯一不同的是它的children是存在一个List<Item> list成员变量里,而并非使用Item的arguments来存储。
Item_cond_or类似不再赘述。
字段节点最主要的成员变量如下:
/**
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;
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的求值的核心方法就是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。
谓语下推核心是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也是没问题的。