MySQL 查询优化器

数据表算法

CREATE TABLE `student` (
  `std_id` int(11) NOT NULL,
  `std_name` varchar(20) NOT NULL DEFAULT '',
  `std_spec` varchar(20) NOT NULL DEFAULT '',
  `std_***` tinyint(4) NOT NULL DEFAULT '0',
  `std_age` tinyint(3) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`std_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `course` (
  `cur_id` int(11) NOT NULL,
  `cur_name` varchar(20) NOT NULL DEFAULT '',
  `cur_credit` tinyint(4) NOT NULL DEFAULT '0',
  `cur_hours` smallint(6) NOT NULL DEFAULT '0',
  PRIMARY KEY (`cur_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `std_cur` (
  `std_id` int(11) NOT NULL,
  `cur_id` int(11) NOT NULL,
  `score` tinyint(4) NOT NULL DEFAULT '0',
  PRIMARY KEY (`std_id`,`cur_id`),
  KEY `std_cur_ibfk_2` (`cur_id`),
  CONSTRAINT `std_cur_ibfk_1` FOREIGN KEY (`std_id`) REFERENCES `student` (`std_id`) ON DELETE NO ACTION ON UPDATE CASCADE,
  CONSTRAINT `std_cur_ibfk_2` FOREIGN KEY (`cur_id`) REFERENCES `course` (`cur_id`) ON DELETE NO ACTION ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

1、普通查询sql

1.1 “*“处理  socket

逻辑处理过程以下所示:函数

JOIN:prepare阶段性能

setup_tables():对查询涉及的表(student表),查看是否存在,设置变量相应的值,为查询准备。(sql\sql_base.cc:7963)测试

setup_wild():将查询中全部“*“展开为对应数据表的字段。主要调用insert_fields()实现。(sql\sql_base.cc:7739)优化

insert_fields():将“*”替换,展开为数据表的字段。(sql\sql_base.cc:8151)ui

set_fields():检查全部字段是否在数据表中。(我的观点:对于替换了“*”的字段,能够省去检查字段的操做,或者对于“*”处理过程能够在set_fields()过程当中处理)。(sql\sql_base.cc:7825)lua

JOIN:optimize阶段spa

make_join_statistics():计算每一个表查询匹配记录数和读取时间。均估计值,并不是实际的值。(sql\sql_select.cc:2651)

choose_plan():查找最优的查询计划。实际处理过程,调用greedy_search()贪婪查找算法实现。因为对一个表进行查询,而且没有任何查询的过滤条件和分组排序等条件,所以整个过程处理一次就结束。(sql\sql_select.cc:4913)

greedy_search():贪婪查找最优执行计划的算法。具体处理过程调用递归函数best_extension_by_limited_search()实现。(sql\sql_select.cc:5219)

best_extension_by_limited_search():对当前的查询计划进行递归查找更优的计划,因为该sql查询须要对全表进行扫描,所以执行一遍操做即终止。(sql\sql_select.cc:5425)

JOIN:exec阶段

do_select():执行查询,并将查询结果经过socket进行写操做。(sql\sql_select.cc:11431)

sub_select():调用evaluate_join_record()逐条取出查询的每条记录。(sql\sql_select.cc:11705)

evaluate_join_record():取出每条记录。(sql\sql_select.cc:11758)

"*"查询student中的全部字段,执行SQL:

SELECT * FROM student;

对应的查询计划以下所示:


由以上处理过程能够知道,对于没有过滤条件以及分组排序条件的状况下,查询优化器处理仅对“*”替换为对应表的字段,而且还对字段进行了再次验证是否在查询的表中。所以,在写sql查询中,不管任何状况下,避免使用“*”,而是直接添加各个字段。而且,仅将须要的字段名进行查询并输出。

    对于全表查询来讲,查询优化器没有任何的优化策略。

1.2 查询字段

    查询表中的字段,进行全表查询时,处理逻辑与1.1相似,惟一区别在于,省去了setup_wild()insert_fields()处理过程。1.2测试与1.1进行对比,从而深刻理解,查询优化器是如何工做。从而更加验证一点,为何不建议使用“*”查询。

查询student中的指定字段,执行SQL:

SELECT std_id, std_name, std_spec, std_***, std_age FROM student;

对应的查询计划以下所示:


1.3 Distinct条件

Distinct的逻辑处理过程以下所示:

JOIN:prepare阶段

setup_tables():同1.1测试。

set_fields():同1.1测试。

JOIN:optimize阶段

make_join_statistics():计算每一个表查询匹配记录数和读取时间。在该测试中,该过程有不一样的操做,主要针对distinct条件。其中,调用add_group_and_distinct_keys()函数查找distinct能够使用的索引。(sql\sql_select.cc:2651)

add_group_and_distinct_keys():查找distinct能够使用的索引。此外,经过调用Item_field::collect_item_field_processor()(sql\sql_item.cc:683)获取全部查询字段,存储到查询列表中。而且合并全部字段的(sql\sql_select.cc:4265)

choose_plan():同1.1测试。

greedy_search():同1.1测试。

best_extension_by_limited_search():同1.1测试。

list_contains_unique_index():查看查询的字段中,是否有惟一索引。若是有查询字段上有惟一索引,那么处理过程将被转化为指定字段的全表查询。(sql\sql_select.cc:13576)

create_distinct_group():根据查询的字段建立一个group,order顺序为给定字段的排列顺序。为将distinct转化为group 条件。(sql\sql_select.cc:15168)

calc_group_buffer():根据查询的字段,计算group by的所需的buffer。(sql\sql_select.cc)

create_tmp_table():根据查询字段,建立临时表。(sql\sql_select.cc:10229)

JOIN:exec阶段

do_select():相似1.1测试,可是当前查询是将查询的结果写到临时表中。

sub_select():相似1.1测试,将查询字段的每条记录取出放到临时表中。

evaluate_join_record():同1.1测试。

change_to_use_tmp_fields():将操做切换到临时表操做。(sql\sql_select.cc:15828)

JOIN::make_simple_join():初始化join。(sql\sql_select.cc:6075)

calc_group_buffer():同上。

do_select():相似1.1测试,不一样之处在于,该查询在临时表中进行。

sub_select():。相似1.1测试,不一样之处在于,记录查询从临时表中获取,并从中过滤掉不符合查询条件的记录。

evaluate_join_record():同1.1测试。

 查询student没有重复的字段执行SQL:

SELECT DISTINCT std_spec, std_***, std_age FROM student;

对应的查询计划以下所示:

由以上处理逻辑能够看出,distinct的优化方式是将distinct转化为group by条件来执行,而且会用到临时表操做。所以,在须要distinct操做时,能够转化为group by操做,来避免转化过程。

将distinct转化GROUP BY后的SQL执行处理逻辑以下所示:

JOIN:prepare阶段

setup_tables():通1.1测试。

set_fields():同1.1测试。

setup_group():初始化group by列表,并调用find_order_in_list()函数,检查group by 的字段是否在select的字段中(调用find_item_in_list(),(sql\sql_base.cc:6835)),并查找group by列表中的字段是否在数据表中 (调用find_item_in_tables(),(sql\sql_base.cc:6602))。(sql\sql_select.cc:15037)

JOIN:optimize阶段

make_join_statistics():同1.3测试。

add_group_and_distinct_keys():相似1.3测试,区别是,这里处理的group条件,而不是distinct条件。

choose_plan():同1.3测试。

greedy_search():同1.3测试。

best_extension_by_limited_search():同1.3测试。

list_contains_unique_index():省去。

create_distinct_group(): 省去。

calc_group_buffer():同1.3测试。

create_tmp_table():同1.3测试。

JOIN:exec阶段

全部操做同1.3测试

查询student中没有重复的字段,执行SQL:

select std_spec, std_***, std_age FROM student GROUP BY std_spec, std_***, std_age;

对应的查询计划以下所示:

经过转化后的测试能够验证,将distinct转化为group by处理过程,能够避免经过程序根据查询字段,转换group by的过程。

1.4 Primary key条件

主键查询的逻辑处理过程以下所示:

JOIN:prepare阶段

setup_tables():通1.1测试。

setup_fields():同1.1测试。

setup_conds():检查查询的where条件中字段是否存在。(sql_base.cc:8379)

JOIN:optimize阶段

optimize_cond():优化查询的where条件,对等值条件调用build_equal_items()(sql\sql_select.cc:8273)函数进行优化,例如(a=b AND c=d AND (b=c OR d=5)转化为等值(a,b,c,d) OR (a=b AND b=c AND d=5));调用propagate_cond_constants()(sql\sql_select.cc:8763)函数将字段等值转换为常量,例如(a=b AND c=d AND d=5转化为a=b AND c=5 AND d=5);调用remove_eq_conds()函数去除常量等值,例如(1=1)(sql\sql_select.cc:9405)。

make_join_statistics():与1.1测试不一样,因为where条件查询的字段是主键,能够使用索引。经过调用update_ref_and_keys()(sql\sql_select.cc:3967)函数,查找是否有使用索引的字段。调用create_ref_for_key()(sql\sql_select.cc:5848)函数为索引建立索引空间。调用join_read_const_table()(sql\sql_select.cc:12109)函数查找索引得到查找的记录。这里由于使用了主键索引,因此只有一行匹配内容,记录数和读取时间均设置为1。因为使用主键索引对单个数据表进行查询,所以不须要对现有的查询进行继续优化,。(sql\sql_select.cc:2651)。

join_read_const_table():经过查找索引获取查询的记录。其中调用join_read_const()(sql\sql_select.cc:12222)函数用于取出数据。

JOIN:exec阶段

do_select():与以前操做不一样,该过程将取出的数据直接发送。

    主键的查询性能较高,仅有一条查询记录,查询计划为const类型。与unique key索引的查询优化器执行过程相似。

 查询student主键字段执行SQL:

SELECT std_id, std_name, std_spec, std_***, std_age FROM student WHERE std_id = 2012072306;

对应的查询计划以下所示:

1.5普通条件查询

    普通where查询条件的逻辑处理过程以下所示:

JOIN:prepare阶段

setup_tables():通1.1测试。

setup_fields():同1.1测试。

setup_conds():同1.4测试。

JOIN:optimize阶段

optimize_cond():同1.4测试。

make_join_statistics():与1.4测试相似,不一样之处在于,因为where条件字段没有索引,所以在查找索引时没有匹配的索引能够使用。因此该函数会调用update_ref_and_keys()函数,查找是否有使用索引的字段,可是不调用create_ref_for_key()和join_read_const_table()。也由于不能经过索引查找,接下来的操做跟1.1测试的全表查询相似。

choose_plan():如下操做同1.1测试。

JOIN:exec阶段

如下操做同1.1测试。

    对普通字段进行where条件查询时,若是查询字段没有索引,将进行全表查询,查询计划类型为all。

 查询student普通字段执行SQL:

SELECT std_id, std_name, std_spec, std_***, std_age FROM student WHERE std_id = 2012072306;

对应的查询计划以下所示:

为查询字段添加索引后,进行一样的操做,查看添加索引后,查询优化器的处理逻辑和查询计划的改变状况。添加索引操做:

ALTER TABLE student ADD INDEX idx_student_name (std_name);

添加索引后,查询优化器的处理逻辑以下所示:

JOIN:prepare阶段

setup_tables():通1.1测试。

setup_fields():同1.1测试。

setup_conds():同1.4测试。

JOIN:optimize阶段

optimize_cond():同1.4测试。

make_join_statistics():与1.4测试不一样,因为where条件查询的字段是非主键,可是能够使用索引。首先经过调用update_ref_and_keys()(sql\sql_select.cc:3967)函数,查找是否有使用索引的字段。在查到能够使用索引后,并不会调用create_ref_for_key()和join_read_const_table()读取记录。而是调用get_quick_record_count()函数估计须要读取的记录数和读取时间,主要处理逻辑调用SQL_SELECT::test_quick_select()函数实现,具体处理逻辑以下所示。

SQL_SELECT::test_quick_select():该函数调用get_mm_tree()(sql\opt_range.cc:5518)函数获取查询树,调用get_key_scans_params()函数获取最优的查找范围,并估计查询的记录数,具体处理逻辑以下。(sql\sql_select.cc:2651)

get_key_scans_params():该函数调用check_quick_select()(sql\opt_range.cc:7519)函数,核心处理逻辑调用check_quick_keys()(sql\opt_range.cc:7628)函数,递归估计经过索引查找的记录的行数,经过调用ha_innodb.cc::records_in_range()(storage\innobase\handler\ha_innodb.cc:7505)估计该范围内索引查找的记录数。(sql\opt_range.cc:4923)

choose_plan():同1.3测试。

greedy_search():同1.3测试。

best_extension_by_limited_search():与以前不一样,由于查询能够使用索引,所以该函数调用best_access_path()(sql\sql_select.cc:4365)函数根据查询计划查找最佳路径。

JOIN:exec阶段

如下操做同1.3测试。

    当查询字段上有索引的状况下,在调用make_join_statistics()函数对查询代价进行估计时,使用索引树进行查找,从而避免了全表查询,并能够在执行阶段提升执行效率。从查询计划的类型来看,使用ref索引类型。

 查询student中普通字段,执行SQL:

SELECT std_id, std_name, std_spec, std_***, std_age FROM student WHERE std_id = 2012072306;

对应的查询计划以下所示:


若是当前where条件查询的结果是离散分布的,而且查询结果占总记录数的很小部分的查询状况,根据业务需求,建议使用索引,能够有效提升查询的效率。

相关文章
相关标签/搜索