1       统计信息 1.1      统计信息收集 show index from nkeys;
函数调用流程:
sql_parse.cc::mysql_execute_command(lex->sql_command == SQLCOM_SHOW_KEYS) ->
sql_show.cc::get_schema_stat_record ->
ha_innobase::info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK | HA_STATUS_TIME)(info函数,实现统计信息收集功能,宏定义说明了需要收集的统计信息的类型) -> dict0dict.c::dict_update_statistics(ib_table)(HA_STATUS_TIME,指定此参数时进行统计信息的重新收集) -> dict_update_statistics_low(统计信息收集主函数,遍历表上的所有index,进行统计信息收集,设置到dict_index与dict_table结构之中) -> btr_estimate_number_of_different_key_vals -> 
a)         dict_index
i.              索引总页面数;叶节点页面数
ii.              索引中不同键值个数,对于索引中的每一列,都需要计算。
for (i = 0; i < BTR_KEY_VAL_ESTIMATE_N_PAGES; i++ )
随机在索引中定位BTR_KEY_VAL_ESTIMATE_N_PAGES = 8次页面。一定是8次,无论实际页面是否小于8. next_rec = page_rec_get_next(rec);
cmp_rec_rec_with_match(rec, next_rec, &matched_fields);
读取页面中的索引项,与前一项进行对比。找到第一个不相同的列值后退出,matched_fields为相同列的个数。 for (j = matched_fields + 1; j <= n_cols; j++)
n_diff[j]++;
根据matched_fields,将所有之后的列对应的n_diff数组++,n_diff[j]++ (其中,n_cols为当前索引中,能够确定unique键值的索引列数) for (j = 0; j <= n_cols; j++)
index->stat_n_diff_key_vals[j] =
((n_diff[j] *index->stat_n_leaf_pages +
BTR_KEY_VAL_ESTIMATE_N_PAGES +
total_external_size + not_emptry_flag) /
(BTR_KEY_VAL_ESTIMATE_N_PAGES + total_external_size)
随机8个page扫描完毕,根据统计信息n_diff,计算最终的index中,每一列不同取值的数量(注意,此处是每一列的不同取值的数量,而非rec_per_key)。 b)         dict_table
i.              表总行数。table->stat_n_rows = index->stat_n_diff_key_vals[dict_index_get_n_unique(index)]; 总行数,为表第一个索引,unique key的个数。
ii.              聚簇索引页面数
iii.              二级索引页面数
iv.              table->stat_initialized = 1; 统计信息为重新收集的
v.              table->stat_modified_counter = 0; 信息收集之后,表上无DML操作发生,统计信息是准确的
 
回到info函数,如果调用info函数时,指定了参数HA_STATUS_CONST,那么info函数将会更新表中每一个索引,每一个索引对应的每一个列组合的rec_per_key取值。(例如:考虑nkeys表的c1c2索引,那么将会计算rec_per_key(c1), rec_per_key(c1c2)的取值,而不会计算rec_per_key(c2),单独指定c2列无意义,并不会选择c1c2索引进行查询)。  
a)         计算rec_per_key
for (I = 0; I < table->s->keys; i++)                                          // 遍历索引
for (j = 0; j < table->key_info[i].key_parts; j++)        // 遍历索引中的所有列
rec_per_key = records / (index->stat_n_diff_key_vals[j+1]);
根据前面收集的不同键值数量,与表记录数量,计算最终,rec_per_key的取值。
b)         调整rec_per_key
rec_per_key = rec_per_key / 2;
由于mysql查询优化,更倾向于使用全表扫描,因此在此处做调整,将rec_per_key减半,减少索引range查询返回记录数,提高查询优化选择索引的概率。
 
1.2     统计信息更新 在以下过程中,INNODB将会更新表的统计信息:
analyze table ha_innobase::analyze ->
info(HA_STATUS_TIME | HA_STATUS_CONST | HA_STATUS_VARIABLE)
open table ha_innobase::open -> 第一次open表时需要调用,后续的open,都不调用
info(HA_STATUS_NO_LOCK | HA_STATUS_VARIABLE | HA_STATUS_CONST)
查询优化前,此时不重新收集统计信息,只计算 sql_select.cc::make_join_statistics ->
info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK);
1.3     统计信息收集总结 在第一次open table时,会重新收集全表统计信息,因此第一次open table是较慢的过程。庆幸的是,Innodb会将打开的表cache起来,因此并不会多次open。 结合mysql上层的查询优化可知,range查询优化并不会使用open table过程中收集的统计信息。在此处看来,也无法使用,因为mysql收集的统计信息过于简单,只能回答rec_per_key,无法回答rec_in_range,rec_in_range需要通过调用records_in_range函数计算。 records_in_table,rec_per_key统计信息,在mysql多表join时使用。多表join,ref的属性,在语句执行前,并不能获得key取值,因此只能通过rec_per_key预估join的内表将会返回多少记录。 是否可以借鉴oracle中的执行计划,硬解析与软解析分离的实现思路?同样为mysql保留执行计划,降低查询优化的代价。从以上的分析看来,mysql的查询优化,并不如想象中高效,同样是一个缓慢的过程。 2       查询优化总结 释放可以借鉴oracle中的统计信息持久化策略,持久化rec_per_key,records_in_table两类十分重要的统计信息,持久化统计信息,基本上能够保证mysql join操作的执行计划的稳定性。