分析 greedy_search函数调用的主要函数:best_extension_by_limited_search(),确定多表连接的最优查询计划
通过穷尽搜索(extension)多表连接的组合方式,确定多表连接的最佳路径,即形成了查询计划,最好的查询路径即是最好的查询计划。
多表连接的算法为:
1. 初始:给定一个只有N个简单表(“remaining_tables”中有N个表)的没有查询计划(join->positions为空)的JOIN对象实例,“join->best_positions”为空,;“join->best_read”的值为一个巨大值(逐渐使用求解出的更小的值为其赋值,最后求得最小值即得到最优路径);
2. 中间过程:
a) 调用best_access_path求解当前表(简单表、或连接后得到的中间过程的临时表—也是局部最优的查询计划)的花费;
b) 递归调用自己求解除当前表外的其他没有连接过的表和所有局部最优查询计划(当前入口参数传入的局部最优查询计划和上步新生成的局部最优查询计划)进行连接;
c) 经过以上2个步骤,则所有可能的连接都搜索完成
d) 循环执行“a)”和“b)”步骤直至满足结束条件(循环使得N个简单表得到遍历)
3. 结束条件:构造的查询计划的搜索深度等于了表的个数N,或者提前构造出了最优的查询计划树(没有可再连接的简单表了);
4. 结果:得到最优的查询计划树存储在“join->best_positions”中,得到最优查询计划树的花费存储在“join->best_read”中。
代码分析:
static void
best_extension_by_limited_search(JOIN *join,//最终的最优计划存储于join->best_positions;最优计划的花费存储于oin->best_read
table_map remaining_tables, //没有发生过连接的表,即等待连接的表的集合,最初所有的简单表都在这个集合中,随着表连接的发生,连接过的表会在此集合中去除;注意这个参数本质上是一个位图,去掉表是相当于从其上去掉位图上的表对应位置的标志
uint idx, //局部查询计划的'join->positions'数组的长度;也代表着当前局部搜索树的深度(MySQL使用深度优先搜索算法构造查询计划,从树叶开始逐层向树顶构造,每个局部查询树的深度值为idx的值);
double record_count,//最优的局部查询计划可得到的记录个数(局部最优查询计划的结果集的记录个数)
ouble read_time,//最优的局部查询计划的花费
int search_depth, //路径搜索的深度,不可无限量的搜索下去,初始值是determine_search_depth函数确定的是最大的搜索深度;在best_extension_by_limited_search函数自己递归调用的过程中,其值逐层递减表示靠近叶子层的查询树已经构造完毕,需要就行继续构造树的上层(远离叶子节点)
uint prune_level) // prune_level,2种取值;0 = EXHAUSTIVE,表示要穷尽搜索; 1 = PRUNE_BY_TIME_OR_ROWS,表示要根据时间或记录数进行“剪除”启发式优化。主要取决于存储引擎的支持(其值来源于上级函数choose_plan()中的join->thd->variables.optimizer_prune_level)。用以“启发”查询优化器减小搜索空间
{
……
for (JOIN_TAB **pos= join->best_ref + idx ; (s= *pos) ; pos++) //遍历remaining_tables中的每个表s
{
table_map real_table_bit= s->table->map; //s的位置代表了s表,此处用位图标识位置
if ((remaining_tables & real_table_bit) && //如果代表表位置的real_table_bit在位图remaining_tables中存在
!(remaining_tables & s->dependent) &&
(!idx || !check_interleaving_with_nj(join->positions[idx-1].table, s)))
{
double current_record_count, current_read_time;
/* Find the best access method from 's' to the current partial plan */
best_access_path(join, s, thd, remaining_tables, idx,
record_count, read_time);
//通过调用best_access_path函数得到局部查询计划的最优花费保存到“join->positions[idx].records_read”和“join->positions[idx].read_time”,然后计算当前最优花费(best_access_path函数体中的最后,为这2个变量赋值)
/* Compute the cost of extending the plan with 's' */
current_record_count= record_count * join->positions[idx].records_read;
current_read_time= read_time + join->positions[idx].read_time;
//以上代码得到了当前表s和当前局部查询计划代表的临时表之间做连接产生的新的最优局部查询计划
//以下代码要用除s表外的其他没有连接过的表和所有局部最优查询计划(当前入口参数传入的局部最优查询计划和上步新生成的局部最优查询计划)进行连接(通过递归调用本函数自己实现)
……
//启发式应用:剪除一些查询计划(被去掉的查询计划可能是有潜力成为最优的查询计划的),则不再是穷尽式搜索,可能导致得不到最优的查询计划。1即是PRUNE_BY_TIME_OR_ROWS
if (prune_level == 1) {……}
//递归调用自己,对本表之外的其他表(代码:idx + 1)进行下一层(代码:search_depth - 1)的探索
if ( (search_depth > 1) && (remaining_tables & ~real_table_bit) ) //去掉当前表s还有没有连接的表存在则递归调用自己,如果不存在“没有连接过的表”则表示最优的查询计划已经找到
{ /* Recursively expand the current partial plan */
swap_variables(JOIN_TAB*, join->best_ref[idx], *pos);
best_extension_by_limited_search(join,
remaining_tables & ~real_table_bit,//去掉当前表s,用除s表外的其他没有连接过的表和所有局部最优查询计划进行连接
idx + 1, //通过位置的增加,表示使用本表之外的其他表
current_record_count,
current_read_time,
search_depth - 1,// 进行下一层的探索
prune_level);
……
}
else //最优的查询计划已经找到
{
current_read_time+= current_record_count / (double) TIME_FOR_COMPARE;
……
}
restore_prev_nj_state(s);
}
}
DBUG_VOID_RETURN;
}