MySQL 查询优化器(三)

二、复合查询算法

    在进行复合查询时,为了体现外链接(left join、right join)和通常联合查询的区别,对student表增长了几条记录,而这几条记录在std_cur和course中都没有对应的记录。sql

2.1 多表联合查询socket

    多表联合查询的逻辑处理过程以下所示:函数

JOIN:prepare阶段测试

setup_tables():对查询涉及的表,逐个查看是否存在,设置变量相应的值,为查询准备。优化

setup_fields():对查询的字段进行检查,不一样于以前1.1的检查,该过程当中若是不指定具体数据表的字段的话,将会对全部查询的数据表进行检查。ui

setup_conds():检查查询的where条件中字段是否存在,一样若是不指定具体数据表的字段,将会对全部查询的数据表进行检查。(sql_base.cc:8379)lua

JOIN:optimize阶段spa

simplify_joins():若是能够将外链接简化为内链接处理,那么简化为内链接处理。此外,若是查询为内链接或者外链接查询使用的表拒绝 NULL值,那么将ON条件添加到where条件中,将表的链接操做转化为联合查询处理。在该测试中,因为没有显示的JOIN ON操做,所以不作以上处理。设计

optimize_cond():优化查询的where条件,对等值条件调用build_equal_items()(sql\sql_select.cc:8273)函数进行优化,查询条件转化为(multiple equal(`test`.`student`.`std_id`, `test`.`std_cur`.`std_id`) 和 multiple equal(101, `test`.`course`.`cur_id`, `test`.`std_cur`.`cur_id`)两个等值条件);调用propagate_cond_constants()(sql\sql_select.cc:8763)函数将字段等值转换为常量;调用remove_eq_conds()函数去除常量等值,例如(1=1)(sql\sql_select.cc:9405)。

make_join_statistics():因为where条件查询的字段是主键,可使用索引。经过调用update_ref_and_keys()(sql\sql_select.cc:3967)函数,查找是否有使用索引的字段。因为查询条件中的cur_id为主键索引,且为const类型。所以,调用create_ref_for_key()(sql\sql_select.cc:5848)函数为索引建立索引空间,调用join_read_const_table()(sql\sql_select.cc:12109)函数查找索引得到查找的记录。对使用索引(非主键)查询的表,调用get_quick_record_count()函数估计须要读取的记录数和读取时间,主要处理逻辑调用SQL_SELECT::test_quick_select()函数实现,具体处理逻辑以下所示。

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

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)估计该范围内索引查找的记录数。

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_select.cc:5425)

get_best_combination():根据查找的最优的查询组合方式,生成查询计划。(sql\sql_select.cc:5794)

JOIN:exec阶段

do_select():执行查询,并将查询结果经过socket进行写操做,具体过程调用sub_select()函数执行。(sql\sql_select.cc:11431)

sub_select():因为是联合查询,该过程会调用evaluate_join_record()逐条取出查询计划中的第一个表的每条记录,根据where条件过滤符合条件的记录,而后经过联合查询条件查找联合查询表中的记录。若是联合查询表中没有索引,则对联合表进行全表扫描;若是有索引,则经过索引进行查找;若是第一个表和联合查询表的联合查询条件都没有索引,则会扫描第一个表的逐行记录,根据where条件过滤符合条件的记录,而后根据联合条件,全表扫描联合表,过滤查询记录。(sql\sql_select.cc:11705)

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

多表联合查询执行SQL:

SELECT student.std_id, std_name, std_spec, std_***, std_age, cur_name, cur_credit, cur_hours, score FROM student, course, std_cur WHERE student.std_id = std_cur.std_id AND course.cur_id = std_cur.cur_id AND course.cur_id = 101;

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

从查询处理逻辑和查询计划能够看出,该过程的处理是:首先分析可知,(course.cur_id = std_cur.cur_id AND course.cur_id = 101)能够转化为const类型。则根据course.cur_id主键索引,能够定位course表的惟一记录,该过程如测试1.3主键查询;而且根据course.cur_id能够经过联合主键索引,定位std_cur表中的记录。其次,根据查询优化器的贪婪算法,对查询组合方式进行估计,选择最优的组合方式,即:std_cur经过索引查找到的记录(记录数:26)联合student表(记录数:31,因为增长了五条记录)进行查询。最后,对std_cur表经过索引查找的记录进行遍历,并经过查询条件student.std_id = std_cur.std_idstudent表中主键查找对应的student表中对应的数据。

2.2 Join查询

    从测试计划来看,从2.2至2.8的测试主要对join的不一样组合方式进行测试,查看查询优化器对不一样join方式的处理方式和产生查询计划的差别。经过测试能够有利于根据查询表的数据量和索引状况,来优化join的查询sql语句。

    测试2.2用于测试student表join联合course表和 std_cur表的结果集,联合条件和2.1测试中的where条件一致。

    具体的查询处理逻辑以下所示:

JOIN:prepare阶段

setup_tables():同2.1测试。

setup_fields():同2.1测试。

setup_conds():相似2.1测试,不一样之处在于,该过程查询ON条件中涉及的字段是否存在。此外,该函数的处理过程是首先查询where条件中的字段,而后查询on条件中的字段,而且还对CHECK OPTION条件进行检查和处理。

JOIN:optimize阶段

simplify_joins():相似2.1测试。因为该过程当中有JOIN ON链接,因此该过程会将ON条件添加到where条件中,而且将显示的JOIN查询简化为多表联合查询。

optimize_cond():同2.1测试。

make_join_statistics():同2.1测试。

join_read_const_table():同2.1测试。

SQL_SELECT::test_quick_select():同2.1测试。

get_key_scans_params():同2.1测试。

choose_plan():同2.1测试。

greedy_search():同2.1测试。

best_extension_by_limited_search():同2.1测试。

get_best_combination():同2.1测试。

JOIN:exec阶段

如下同2.1测试。

 Join查询执行SQL:

SELECT student.std_id, std_name, std_spec, std_***, std_age, cur_name, cur_credit, cur_hours, score FROM student JOIN(course, std_cur) ON (student.std_id=std_cur.std_id AND std_cur.cur_id=course.cur_id AND course.cur_id = 101);

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

经过以上测试能够看出,从处理逻辑和执行计划来看,多表联合查询和join查询的处理逻辑和执行过程是一致的。也就是说联合查询的表用“,”和join查询时同样的。所不一样的是,在setup_conds()中须要对ON条件进行处理,在simplify_joins()中将ON条件添加到where条件进行处理,最终join查询处理过程转化多表联合查询。

    此外,经过查看MySQL官方文档发现,“,”进行联合查询,实际是进行cross join查询,而join、cross join、inner join的操做是同样的,能够互相替换的[1]。

若是从理论上不理解缘由的话,能够以简单的处理逻辑来理解。具体来讲,对于多表联合查询操做、join、cross join以及inner join等链接操做来讲,MySQL的处理逻辑都是同样的。

首先,MySQL对待where条件和on条件的处理逻辑是相同,只有处理的前后(先处理where条件,后处理on条件)。

而后,将ON条件添加到where条件中,根据查询的条件查看数据表中的索引是否可使用,若是是主键查询且查询结果只有一条的记录,直接获取查询结果;若是是索引查询(ref类型),调用get_quick_record_count()获取查询记录的行数并估计使用的时间,并生成查询树;若是没有索引,则为全表查询。特别的,该过程当中对于联合主键索引来讲,若是查询条件中只对联合主键的一个进行查询时,查询处理过程与索引查询(ref类型)一致。

接下来,经过调用best_extension_by_limited_search()函数,进行不一样的组合方式,查找最优的查询组合方式,生成查询计划。所以,在该过程当中,表的顺序是没有关系的,即:a join b和b join a的效果是同样的,对查询计划没有影响。

最后,执行查询时,逐条查询驱动表(该表是指查询计划中的第一个表)的每条记录,根据where条件过滤查询结果,而后在联合查询表中,查找符合联合条件的记录。逐条查询驱动表的每条记录并不是必定是全表扫描该表,若是是索引查询,则经过生成的查询树,逐条查询每条记录,不然进行全表扫描。对应的联合查询表,根据联合查询条件进行过滤查询记录。一样若是联合查询表的对应字段可使用索引,那么使用索引定位记录;若是没有可使用的索引,则进行全表扫描。

2.3 JOIN嵌套查询

    测试2.3主要用于比较两个JOIN的联合查询对查询计划的影响,以及查询优化器处理逻辑的差别。

    具体的查询处理逻辑以下所示:

JOIN:prepare阶段

setup_tables():同2.1测试。

setup_fields():同2.1测试。

setup_conds():检查where条件和ON条件中的字段是否存在,一样若是不指定具体数据表的字段,将会对全部查询的数据表进行检查。(sql_base.cc:8379)

JOIN:optimize阶段

simplify_joins():同2.2测试。此外,因为有JOIN ON的嵌套操做,所以增长了一次处理过程。

optimize_cond():同2.1测试。

make_join_statistics():同2.1测试。

join_read_const_table():同2.1测试。

SQL_SELECT::test_quick_select():同2.1测试。

get_key_scans_params():同2.1测试。

choose_plan():同2.1测试。

greedy_search():同2.1测试。

best_extension_by_limited_search():同2.1测试。

get_best_combination():同2.1测试。

JOIN:exec阶段

如下同2.1测试

Join嵌套查询执行SQL:

SELECT student.std_id, std_name, std_spec, std_***, std_age, cur_name, cur_credit, cur_hours, score FROM student JOIN std_cur ON student.std_id = std_cur.std_id JOIN course ON std_cur.cur_id=course.cur_id WHERE course.cur_id = 101;

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

经过以上测试能够看出,与测试2.2的处理逻辑基本一致。不一样之处是不但有ON条件的处理,还增长了where条件的处理,最终join查询处理过程转化多表联合查询。所以,内链接的嵌套JOIN查询,与多表联合查询的处理过程是一致的,只是在处理过程当中,须要首先对内嵌的JOIN进行处理。

2.4 JOIN嵌套查询(普通字段)

    测试2.3中的JOIN嵌套查询中,where字段为course表的主键索引,而且是常量条件和一条记录。为了更深刻的研究嵌套JOIN查询的处理逻辑,设计2.4测试。

    具体的查询处理逻辑以下所示:

JOIN:prepare阶段

setup_tables():同2.1测试。

setup_fields():同2.1测试。

setup_conds():同2.3测试。

JOIN:optimize阶段

simplify_joins():同2.3测试。

optimize_cond():同2.1测试。

make_join_statistics():调用update_ref_and_keys()(sql\sql_select.cc:3967)函数,因为where条件的字段没有索引,所以找不到可用的索引。而且where条件字段不是主键索引,所以不会调用函数create_ref_for_key()(sql\sql_select.cc:5848)和函数join_read_const_table()(sql\sql_select.cc:12109)来查询const 记录。尽管表连接操做使用索引,可是因为where条件中的字段不能使用索引,所以连接操做也没法使用索引进行快速获取读取的记录数和读取时间。所以,也不能调用get_quick_record_count()函数和SQL_SELECT::test_quick_select()函数。

choose_plan():同2.1测试。

greedy_search():同2.1测试。

best_extension_by_limited_search():相似2.1测试。不一样之处在于因为where条件不是常量const查询,因此在进行best_access_path()查找最优的查询计划时,对三个表进行不一样的组合,获取最优的查询记录数和最优的查询时间,从而最终生成查询计划。

get_best_combination():相似2.1测试。不一样之处在于,course表进行全表扫描,其余表调用create_ref_for_key()函数进行索引查询,并生成最终的查询计划。

JOIN:exec阶段

如下与2.1测试相似,不一样之处在于这里是三个表的join查询。

    最后的查询执行过程详细以下:首先对course进行全表扫描,当course中找到符合查询条件的记录,经过主键查找std_cur表中的记录,再根据std_cur表中的符合主键std_id查找student表中的记录。查找记录数为:(20+1*26*1),其中20表示全表扫描course表的每条记录;1表示只有一条记录符合查询条件;26表示符合条件的cur_id在std_cur中有26条记录符合查询条件,而且经过符合主键查找;1表示经过std_cur中的std_id主键查找student表。

Join嵌套查询(普通字段)执行SQL:

SELECT student.std_id, std_name, std_spec, std_***, std_age, cur_name, cur_credit, cur_hours, score FROM student JOIN (std_cur JOIN course ON std_cur.cur_id=course.cur_id) ON student.std_id = std_cur.std_id WHERE course.cur_name = 'PHP';

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

经过以上分析能够看出,不一样于以前的join查询之处在于,当使用主键查询时,因为是常量查询,会直接取出查询记录,而且转化表连接条件(std_cur.cur_id = course.cur_id)为确切的查询常量条件(std_cur.cur_id = 101),能够直接经过索引建立查询树。而且在执行查询时,为两个表的联合查询。而当普通查询条件时,则查询为全表扫描,而且也不能经过连接条件进行任何的简化操做。只能在查询执行时,经过索引提升联合查询表的查询效率。

    此外,从查询计划能够看出,std_cur表查询的记录行数实际上为26,而查询计划中的记录行数为13。这是因为在best_extension_by_limited_search()中查找最优计划时,进行估计查询记录行数和查询时间时,得出的一个估计值,并不是实际值。

相关文章
相关标签/搜索