您的位置:首页 > 数据库 > MySQL

MySQL 查询优化器(三)

2015-09-29 00:00 651 查看
摘要: MySQL

2、复合查询

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

2.1 多表联合查询

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

JOIN:prepare阶段

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

setup_fields():对查询的字段进行检查,不同于之前1.1的检查,该过程中如果不指定具体数据表的字段的话,将会对所有查询的数据表进行检查。

setup_conds():检查查询的where条件中字段是否存在,同样如果不指定具体数据表的字段,将会对所有查询的数据表进行检查。(sql_base.cc:8379)

JOIN:optimize阶段

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_id从student表中主键查找对应的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()中查找最优计划时,进行估计查询记录行数和查询时间时,得出的一个估计值,并非实际值。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: