目录文档:MySQL 源码|源码剖析文档目录
涉及内容:
- 函数:sql/select.cc >
count_field_types
- 类:sql/temp_table_param.h >
Temp_table_param
- 数据成员:sql/sql_optimizer.h >
JOIN::tmp_table_param
在优化器主函数 JOIN::optimize()
中,多次调用了 count_field_types
函数用于统计字段类型的数量,并将其结果记录在用于临时表配置的 Temp_table_param
类型对象中。在调用 JOIN::optimize()
函数时,提供了 Temp_table_param
类型的临时表配置对象,即 JOIN::tmp_table_param
。
Temp_table_param
类
Temp_table_param
对象包括在创建和使用临时表时所用的参数,借助此对象创建的临时表仅供查询执行引擎内部使用。Temp_table_param
类中的主要逻辑源码如下:
class Temp_table_param {
public:
Mem_root_array<Copy_field> copy_fields;
uchar *group_buff;
Func_ptr_array *items_to_copy; /* Fields in tmp table */
KEY *keyinfo;
ha_rows end_write_records{HA_POS_ERROR};
uint func_count;
uint sum_func_count;
uint hidden_field_count;
uint group_parts, group_length, group_null_parts;
bool allow_group_via_temp_table{true};
uint outer_sum_func_count;
bool using_outer_summary_function;
CHARSET_INFO *table_charset;
bool schema_table;
bool precomputed_group_by;
bool force_copy_fields;
bool skip_create_table;
bool bit_fields_as_long;
bool can_use_pk_for_unique;
bool force_hash_field_for_unique{false};
bool m_window_frame_buffer{false};
enum {
TTP_UNION_OR_TABLE,
TTP_EXCEPT,
TTP_INTERSECT
} m_operation{TTP_UNION_OR_TABLE};
bool needs_set_counter() { return m_operation != TTP_UNION_OR_TABLE; }
bool m_last_operation_is_distinct{false};
Window *m_window;
void cleanup() { copy_fields.clear(); }
};
其中的数据成员包括:
copy_fields
(Mem_root_array<Copy_field>
)group_buff
(uchar *
)items_to_copy
(Func_ptr_array *
)keyinfo
(KEY *
):在创建临时表之后,指向根据表的用途(如分组、去重等)所创建的表上的索引。这样的索引最多只有一个。end_write_records
(ha_rows
):临时表的行数上限(最大行数),或者使用HA_POS_ERROR
表示没有限制。当写入表时,此限制由MaterializeIterator
强制执行。func_count
(uint
):查询中的项目数量,包括聚合函数(如SUM
)、非聚合函数(如RAND
)、窗口函数以及字段。 同时也会计算在窗口函数或聚合函数中引用的函数,例如SELECT SUM(RAND())
的计数为 2。由count_field_types
函数设置。sum_func_count
(uint
):查询中具有聚合函数的字段数量。需要注意的是,优化器可能会选择通过将这些字段替换为常量来优化掉它们,在这种情况下,sum_func_count
需要被更新。由optimize_aggregated_query
函数和count_field_types
函数设置。hidden_field_count
(uint
)group_parts
(uint
)group_length
(uint
)group_null_parts
(uint
)allow_group_via_temp_table
(bool
):是否允许运行GROUP BY
并写入到临时表中,即同时进行多种聚合操作而不需要有序的输入。这通常是允许的,但目前不支持用于聚合 UDF、带有DISTINCT
的聚合函数或ROLLUP
。需要注意的是,即使这种情况是允许的,优化器也可能选择不使用临时表,因为直接读取索引会更加高效。outer_sum_func_count
(uint
):外部聚合函数的数量,即在这个子查询外部的查询块中被聚合的集合函数的数量。由count_field_types
函数设置。外部聚合函数,即在当前子查询之外的更高层级的查询结构中执行的聚合操作。using_outer_summary_function
(uint
):当至少有一个外部聚合函数时启用,当使用DISTINCT
时需要。由create_tmp_table
函数设置。table_charset
(CHARSET_INFO *
)schema_table
(bool
)precomputed_group_by
(bool
):如果GROUP BY
及其聚合函数已经通过某种表访问方法(例如通过 loose index scan)计算完毕,则此值为真。在这种情况下,查询执行不应再进行聚合操作,并应将聚合函数视为普通函数处理。force_copy_fields
(bool
)skip_create_table
(bool
):如果为真,则在创建结果表时实际上不创建表处理器。这允许范围优化器之后添加索引。用于物化派生表 / 视图。由Table_ref::update_derived_keys
函数设置。bit_fields_as_long
(bool
):如果为真,则在create_tmp_table
中调用create_tmp_field
,将所有 BIT 字段转换为 64 位长整型。这是针对 MEMORY 表无法对 BIT 列建立索引这一限制的解决方法。can_use_pk_for_unique
(bool
):唯一索引是否可以提升为主键。force_hash_field_for_unique
(bool
):是否应始终通过隐藏的哈希字段来实现唯一键,而不是使用唯一索引。这对于混合UNION ALL
、UNION
、DISTINCT
的查询来说是必须的,详见create_result_table
函数。m_window_frame_buffer
(bool
):这个临时表是否用于窗口的帧缓冲。m_operation
(枚举值):用于INTERSECT
和EXCEPT
计算。m_last_operation_is_distinct
(bool
):用于INTERSECT
和EXCEPT
计算。m_window
(Window *
):如果是某个窗口的输出表,则指向窗口。
其中的函数成员包括:
needs_set_counter()
:返回是否需要设置一个计数器来统计重复项(用于 EXCEPT 和 INTERSECT 计算)。cleanup()
:清空copy_fields
数据成员。
count_field_types
函数
count_field_types
函数用于计算字段、函数以及求和函数(类型为 SUM_FUNC_ITEM
的项)的数量,以便被 create_tmp_table()
使用,并将这些数量存储在第 2 个参数 param
的 Temp_table_param
类型对象中。如果需要的话,它还会更新 allow_group_via_temp_table
的属性。
count_field_types
函数的原型如下:
// sql/sql_select.h
void count_field_types(const Query_block *query_block, Temp_table_param *param,
const mem_root_deque<Item *> &fields,
bool reset_with_sum_func, bool save_sum_fields);
count_field_types
函数中的参数含义如下:
query_block
(Query_block *
):查询的Query_block
param
(Temp_table_param *
):临时表的参数fields
(mem_root_deque<Item *> &
):需要用于统计的字段的列表reset_with_sum_func
(bool
):是否需要重置函数的with_sum_func
save_sum_fields
(bool
):是否在接受到相同的参数时,按create_tmp_table()
函数预期的方式进行统计,即在字段列表中保留Item_sum_*
函数
count_field_types
函数中的逻辑如下:
Step 1|初始化 Temp_table_param
中数据成员:将 sum_func_count
(查询中具有聚合函数的字段数量)置为 0,将 func_count
(查询中的项目数量)置为 fields
参数的数量,将 hidden_field_count
置为 0,将 outer_sum_func_count
(外部聚合函数的数量)置为 0。
param->sum_func_count = 0;
param->func_count = fields.size();
param->hidden_field_count = 0;
param->outer_sum_func_count = 0;
Step 2|如果 GROUP BY
及其聚合函数已经通过松散索引扫描并计算完毕,则 param->precomputed_group_by
为 true
,此时将 save_sum_fields
置为 true
。
save_sum_fields |= param->precomputed_group_by;
Step 3|遍历每个字段以统计字段信息,具体地,对于每个字段执行以下逻辑:
- 如果是非窗口函数的聚集函数:
real_type == Item::SUM_FUNC_ITEM && !real->m_is_window_function
- 如果当前字段不是常量(
!field->const_item()
)- 如果聚集函数(
sum_item
)不允许通过临时表聚集,则将Temp_table_param
也置为不允许通过临时表聚集 - 累加查询中聚集函数的字段数量(
sum_func_count
) - 使用聚集函数中的参数数量(
sum_item->argument_count()
),累加查询中的项目数量(file_count
)
- 如果聚集函数(
- 如果当前字段是常量,且需要在接受到相同的参数时,按
create_tmp_table()
函数预期的方式进行统计(save_sum_fields
),即在字段列表中保留Item_sum_*
函数- 累加查询中的项目数量(
func_count
) - 累加查询中聚集函数的字段数量(
sum_func_count
)
- 累加查询中的项目数量(
- 如果当前字段不是常量(
- 如果是窗口函数的聚集函数:
real_type == Item::SUM_FUNC_ITEM && real->m_is_window_function
- 使用窗口函数中的参数数量(
window_item->argument_count()
),累加查询中的项目数量(file_count
)
- 使用窗口函数中的参数数量(
- 如果不是聚集函数:
- 如果需要重置函数的
with_sum_func
,则调用field->reset_aggregation()
函数 - 如果当前字段是聚集字段,则累加外部聚合函数的数量(
outer_sum_func_count
)
- 如果需要重置函数的
for (Item *field : fields) {
Item *real = field->real_item();
Item::Type real_type = real->type();
if (real_type == Item::SUM_FUNC_ITEM && !real->m_is_window_function) {
if (!field->const_item()) {
Item_sum *sum_item = down_cast<Item_sum *>(field->real_item());
if (sum_item->aggr_query_block == query_block) {
if (!sum_item->allow_group_via_temp_table)
param->allow_group_via_temp_table = false; // UDF SUM function
param->sum_func_count++;
param->func_count += sum_item->argument_count();
}
} else if (save_sum_fields) {
if (field->type() != Item::SUM_FUNC_ITEM) {
param->func_count++; // TODO: Is this really needed?
param->sum_func_count++;
}
}
} else if (real_type == Item::SUM_FUNC_ITEM) {
Item_sum *window_item = down_cast<Item_sum *>(real);
param->func_count += window_item->argument_count();
} else {
if (reset_with_sum_func) field->reset_aggregation();
if (field->has_aggregation()) param->outer_sum_func_count++;
}
}