MySQL数据库InnoDB存储引擎查询优化器实现的分析之统计信息
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操作的执行计划的稳定性。
本系列文章主目录:MySQL数据库InnoDB存储引擎查询优化器实现的分析
建议继续学习:
- Mysql查询优化器浅析(上) (阅读:2541)
- MySQL数据库InnoDB存储引擎查询优化器实现的分析之单表查询 (阅读:2490)
- MySQL数据库InnoDB存储引擎查询优化器实现的分析之单表unique查询 (阅读:2111)
- Mysql查询优化器浅析(下) (阅读:2098)
- MySQL数据库InnoDB存储引擎查询优化器实现的分析之多表简单JOIN查询 (阅读:2034)
- MySQL数据库InnoDB存储引擎查询优化器实现的分析 (阅读:1986)
- MySQL数据库InnoDB存储引擎查询优化器实现的分析之optimizer_search_depth参数 (阅读:1916)
- MySQL数据库InnoDB存储引擎查询优化器实现的分析之附录 (阅读:1866)
- MySQL数据库InnoDB存储引擎查询优化器实现的分析之best_access_path函数分析 (阅读:1559)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:Eugene 来源: MySQLOPS 数据库与运维自动化技术分享
- 标签: 查询优化器
- 发布时间:2012-01-08 22:29:40
- Mysql查询优化器浅析(上)
- MySQL数据库InnoDB存储引擎查询优化器实现的分析之单表查询
- MySQL数据库InnoDB存储引擎查询优化器实现的分析之单表unique查询
- Mysql查询优化器浅析(下)
- MySQL数据库InnoDB存储引擎查询优化器实现的分析之多表简单JOIN查询
- MySQL数据库InnoDB存储引擎查询优化器实现的分析
- MySQL数据库InnoDB存储引擎查询优化器实现的分析之optimizer_search_depth参数
- MySQL数据库InnoDB存储引擎查询优化器实现的分析之附录
- MySQL数据库InnoDB存储引擎查询优化器实现的分析之best_access_path函数分析
- [42] IOS安全–浅谈关于IOS加固的几种方法
- [41] 界面设计速成
- [40] 图书馆的世界纪录
- [40] 如何拿下简短的域名
- [39] Oracle MTS模式下 进程地址与会话信
- [38] android 开发入门
- [37] 【社会化设计】自我(self)部分――欢迎区
- [35] 读书笔记-壹百度:百度十年千倍的29条法则
- [35] 视觉调整-设计师 vs. 逻辑
- [33] 程序员技术练级攻略