MySQL 查询优化器(二)

谁都会走 提交于 2020-04-07 05:36:48

1.6多个查询字段(常量条件)

    多个查询字段的查询处理逻辑如下所示:

JOIN:prepare阶段

setup_tables():同1.1测试。

setup_fields():同1.1测试。

setup_conds():同1.4测试。

JOIN:optimize阶段

optimize_cond():类似1.4测试,不同之处在于查询的where条件中有恒等常量,在优化过程中会调用remove_eq_conds将1=1条件删除。

make_join_statistics():与1.4测试类似,由于where条件查询有两个,并且其中一个条件可以通过索引查询。因此首先通过调用update_ref_and_keys()(sql\sql_select.cc:3967)函数,查找可以使用索引的字段。

SQL_SELECT::test_quick_select():同1.5索引测试

get_key_scans_params():同1.5索引测试。

choose_plan():同1.3测试。

greedy_search():同1.3测试。

best_extension_by_limited_search():同1.5索引测试。

JOIN:exec阶段

以下操作同1.3测试。

    通过测试可以看出,对于常量等式对查询优化器来说没有任何意义,会在optimize_conds时将常量等式删除。建议在查询的where条件中不要出现对查询没有意义的条件,尽管查询优化器会将没有意义的查询条件去除,并等式的右值条件尽可能的转化为常量,例如将类似(a=b, b=1)类型转化为(a=1, b=1)。但从优化角度来看,尽可能在sql语句中就避免这些优化逻辑。查询计划的类型为ref,利用索引查找。

查询student中多个普通字段(包含常量条件),执行SQL:

SELECT std_id, std_name, std_spec, std_***, std_age FROM student WHERE std_name="fff" AND std_age > 20 AND 1=1;

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

查询计划跟单个查询条件的索引查询的执行相同,这说明增加非索引字段的查询,主要用于从索引查询的结果中过滤记录。如果添加的索引不能过滤掉大多数的查询记录的话,普通查询条件过滤记录的代价也会较大。因此,建立索引的目的应该是尽可能的过滤掉查询记录,那么增加索引才有效,否则索引查询反而会增加查询的代价。

1.7 LIMIT条件

    Limit条件查询的逻辑处理流程如1.5普通条件查询类似,唯一不同点在于JOIN::exec()处理过程中,取结果集时,根据limit限制的值来进行过滤。因此,当查询结果集较多的情况下,limit条件可以减少结果集传递的代价。

查询studentlimit条件执行SQL:

SELECT std_id, std_name, std_spec, std_***, std_age FROM student WHERE std_spec = "computer" LIMIT 5;

1.8 IN条件

    In条件查询的逻辑处理流程如下所示:

JOIN:prepare阶段

setup_tables():同1.1测试。

setup_fields():同1.1测试。

setup_conds():同1.4测试。

JOIN:optimize阶段

optimize_cond():同1.4测试。

make_join_statistics():与1.5索引测试类似,不同之处在于调用update_ref_and_keys()(sql\sql_select.cc:3967)函数时,查找查询字段是否可以用索引字段时,没有找到可以使用的索引。但是由于查询字段上有索引,因此调用get_quick_record_count()函数的实现逻辑SQL_SELECT::test_quick_select()函数构建查询树时,转化为OR的方式处理,并将查找的键值进行合并,形成查询范围。

SQL_SELECT::test_quick_select():同1.5索引测试

get_key_scans_params():同1.5索引测试。

choose_plan():同1.3测试。

greedy_search():同1.3测试。

best_extension_by_limited_search():同1.5测试。尽管查询字段有索引,但是调用update_ref_and_keys()对查询字段进行检查是否可以使用索引时,没有可以使用的索引。因此,不同于1.5的索引测试的过程,而是与普通查询测试相同。

JOIN:exec阶段

以下操作类似1.3测试。不同之处在于查询的范围通过搜索查询树,减小了查询的范围。

查询studentin条件(索引)执行SQL:

SELECT std_id, std_name, std_spec, std_***, std_age FROM student WHERE std_name in ("bbb","ccc","ddd");

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


从逻辑处理过程和查询计划可以看出,IN条件的处理是转化为OR的条件来处理,这一点从查询树的构建可以看出。查询计划类型为range,没有使用索引直接查询,而是通过索引查找来构建查询树,减小查询的范围。

    IN条件的查询字段为普通字段时,由于查询字段无索引,因此,不会通过调用get_quick_record_count()函数的实现逻辑SQL_SELECT::test_quick_select()函数构建查询树,从而形成查询范围。而是直接执行JOIN::exec中进行全表扫描,通过查询条件过滤查询结果。

查询studentin条件(普通字段)执行SQL:

SELECT std_id, std_name, std_spec, std_***, std_age FROM student WHERE std_spec in ("math","information");

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


通过查询计划可以看出,对没有索引的字段使用IN操作跟普通的1.5的普通字段查询测试相同。

    通过对IN条件的整体测试发现,当查询的字段存在索引的情况下,查询优化器会利用查询字段的索引,构建查询树,减小查询的范围,然后通过查询优化数进行全扫描并返回查询结果。而在查询字段没有索引的情况下,查询计划和查询逻辑跟普通查询条件进行全表扫描一样,在执行全表扫描时,通过查询条件过滤查询结果。

1.9 GROUP BY条件、ORDER BY条件、HAVING条件联合

    之所以将三个条件联合查询,而非单独测试,是由于这些条件一般都是组合使用才有效果。并且,单独测试不会凸显查询条件的全部处理逻辑,不利于更好的理解。

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

JOIN:prepare阶段

setup_tables():同1.1测试。

setup_fields():同1.1测试。

setup_conds():同1.4测试。

setup_order():初始化order by列表。根据order by的字段,改变查询列表中查询字段的order by顺序。并调用find_order_in_list()函数,检查order by 的字段是否在select的字段中(调用find_item_in_list(),(sql\sql_base.cc:6835)),并查找order by列表中的字段是否在数据表中 (调用find_item_in_tables(),(sql\sql_base.cc:6602))。(sql\sql_select.cc:15037)(sql\sql_select.cc:14996)

setup_group():初始化group by列表,处理逻辑跟setup_order()的处理逻辑相同。调用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)(sql\sql_select.cc:14996)

having处理:having的初始化过程没有以具体的函数形式给出,但是处理逻辑类似group by。主要检查having条件的字段是否在查询列表中。由于having条件是查询字段的别名,因此在比较了查询列表后,发现该字段即结束。

JOIN:optimize阶段

optimize_cond():类似1.4测试,但不同之处在于进行优化的是having条件。

make_join_statistics():与1.3转化为group by后的测试类似。

choose_plan():同1.3测试。

greedy_search():同1.3测试。

best_extension_by_limited_search():同1.3测试。

calc_group_buffer():同1.3测试。

create_tmp_table():同1.3测试。

JOIN:exec阶段

以下操作同1.3测试类似,区别在于对临时表操作时,临时结果的过滤方式不同。生成临时结果的过程中,会调用聚合函数进行处理。

    从处理逻辑来看,在JOIN::prepare阶段增加了初始化group、order、having条件。由于查询字段没有索引,因此需要进行全表扫描。与普通查询不同的执行过程在于在临时表操作时,临时结果的过滤处理不同。

查询studentgroup byorder byhaving联合条件执行SQL:

SELECT std_spec, COUNT(std_spec) cnt FROM student GROUP BY std_spec HAVING cnt > 3 ORDER BY cnt;

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


从查询计划来看,与1.3测试完全一致。查询计划类型为ALL,进行全表扫描。

1.10 UNION条件

    Union条件的查询处理逻辑如下所示:

mysql_union():Union条件处理逻辑。调用st_select_lex_unit::prepare()函数(sql\sql_union.cc:1172)对Union中的每条语句进行处理。

st_select_lex_unit::prepare():逐条执行JOIN:prepare()处理逻辑,为查询处理做准备工作,并调用select_union::create_result_table()(sql\sql_union.cc:117)函数创建临时表。

JOIN:prepare阶段:主要处理过程同1.4测试。

st_select_lex_unit::exec():逐条执行JOIN:optimize()处理逻辑,对每条查询语句进行优化。逐条执行JOIN:exec()处理逻辑,执行查询操作。

JOIN:optimize阶段:对于Union中的不同查询字段类型的语句,处理逻辑有所不同。Union的第一句的处理逻辑同1.4主键查询测试。Union的第二句的处理逻辑同1.5测试的索引字段处理。Union的第三句的处理逻辑同1.5测试的普通字段处理。

JOIN:exec阶段:对不同的Union中不同查询字段类型的语句,处理逻辑不同,并将查询的结果存储到临时表中。同JOIN:optimize阶段的对应关系相同。

st_select_lex_unit::init_prepare_fake_select_lex():构建一条查询语句,获取查询结果。

mysql_select():同1.1测试,获取查询结果。

从处理逻辑来看,UNION条件的处理相当于分别对不同的查询子句根据对应的查询条件类型进行处理。处理结束后,对分别的查询结果再次进行查询过滤。

查询studentunion条件执行SQL:

SELECT std_id, std_name, std_spec, std_***, std_age FROM student WHERE std_id=2012072306 UNION SELECT std_id, std_name, std_spec, std_***, std_age FROM student WHERE std_name="bbb" UNION SELECT std_id, std_name, std_spec, std_***, std_age FROM student WHERE std_spec="math";

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


 从查询计划来看,查询类型根据UNION中各个子句的查询字段类型,生成各自的查询计划。最后对查询结果,进行进一步的过滤查询。




易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!