0
点赞
收藏
分享

微信扫一扫

MySQL Hints - 表级优化提示

概念描述

MySQL有一些表级别的Hint,利用这些Hint配合索引、数据倾斜等特点,有时可以大幅度的提高SQL性能,以达到优化目的。

表级优化提示(Table-Level Optimizer Hints),常用的Hint有4种:

  • BKA, NO_BKA:启用或禁用BKA算法对表进行JOIN。
  • BNL, NO_BNL:启用或禁用BNL算法对表进行JOIN。
  • DERIVED_CONDITION_PUSHDOWN, NO_DERIVED_CONDITION_PUSHDOWN:启用或禁用派生表条件下推(从MySQL 8.0.22版本开始)
  • MERGE, NO_MERGE:启用或禁用视图合并(如视图、子查询、CTE等)。

这里还要说明以下3点:

  • 建议读者先了解下NLJ、BNL、BKA、HASH算法。
  • 派生表条件下推,在其它关系型数据库中早已存在,而MySQl到8.0.22版本才引入,这个优化是常用的技巧之一。
  • 视图合并,MySQL对于复杂些的子查询优化的不是很好,但此方法也是常用的技巧之一,类似于把SQL中的子查询展开成基表访问。

测试验证

创建测试数据如下:

drop table if exists t1;
CREATE TABLE t1 (
  id int NOT NULL AUTO_INCREMENT,
  name varchar(20) DEFAULT NULL,
  age int DEFAULT NULL,
  PRIMARY KEY (id),
  KEY ix_age (age)
) ENGINE=InnoDB;
insert into t1(id, name, age) values(1, 'name1', 10);
insert into t1(id, name, age) values(2, 'name2', 10);
insert into t1(id, name, age) values(3, 'name3', 20);
insert into t1(id, name, age) values(4, 'name4', 20);
insert into t1(id, name, age) values(5, 'name5', 30);
insert into t1(id, name, age) values(6, 'name6', 30);
insert into t1(id, name, age) values(7, 'name3', 40);
insert into t1(id, name, age) values(8, 'name4', 40);
insert into t1(id, name, age) values(9, 'name5', 50);
insert into t1(id, name, age) values(10, 'name6', 50);

drop table if exists t2;
create table t2 like t1;
insert into t2 select * from t1;

mysql> select * from t1;
+----+-------+------+
| id | name  | age  |
+----+-------+------+
|  1 | name1 |   10 |
|  2 | name2 |   10 |
|  3 | name3 |   20 |
|  4 | name4 |   20 |
|  5 | name5 |   30 |
|  6 | name6 |   30 |
|  7 | name3 |   40 |
|  8 | name4 |   40 |
|  9 | name5 |   50 |
| 10 | name6 |   50 |
+----+-------+------+
10 rows in set (0.01 sec)

-- 查看optimizer_switch变量
mysql> show variables like 'optimizer_switch';
+------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Variable_name    | Value                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
+------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| optimizer_switch | index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=on,mrr_cost_based=on,block_nested_loop=on,batched_key_access=off,materialization=on,semijoin=on,loosescan=on,firstmatch=on,duplicateweedout=on,subquery_materialization_cost_based=on,use_index_extensions=on,condition_fanout_filter=on,derived_merge=on,use_invisible_indexes=off,skip_scan=on,hash_join=on,subquery_to_derived=off,prefer_ordering_index=on,hypergraph_optimizer=off,derived_condition_pushdown=on |
+------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
--

1. BKA, NO_BKA:启用或禁用BKA算法对表进行JOIN.

-- 启用BKA,从Extra列可看出被驱动表走了BKA算法
mysql> explain
    -> select /*+ BKA(a, b) */ a.*
    -> from t1 a
    -> inner join t2 b
    -> on a.age=cast(b.age as char)
    -> ;
+----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+-----------------------------------------------------+
| id | select_type | table | partitions | type  | possible_keys | key    | key_len | ref  | rows | filtered | Extra                                               |
+----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+-----------------------------------------------------+
|  1 | SIMPLE      | b     | NULL       | index | NULL          | ix_age | 5       | NULL |   10 |   100.00 | Using index                                         |
|  1 | SIMPLE      | a     | NULL       | ref   | ix_age        | ix_age | 5       | func |    2 |   100.00 | Using where; Using join buffer (Batched Key Access) |
+----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+-----------------------------------------------------+
2 rows in set, 1 warning (0.02 sec)

mysql> explain analyze
    -> select /*+ BKA(a, b) */ a.*
    -> from t1 a
    -> inner join t2 b
    -> on a.age=cast(b.age as char)
    -> ;
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Batched key access inner join  (actual time=19.460..27.760 rows=20 loops=1)
    -> Batch input rows
        -> Covering index scan on b using ix_age  (cost=1.25 rows=10) (actual time=0.269..1.335 rows=10 loops=1)
    -> Filter: (cast(a.age as double) = cast(cast(b.age as char charset utf8mb4) as double))  (actual time=17.501..24.526 rows=20 loops=1)
        -> Multi-range index lookup on a using ix_age (age=cast(b.age as char charset utf8mb4))  (actual time=17.173..22.687 rows=20 loops=1)
 |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

-- 启用BKA,但当优化器判断有更低成本的执行计划时,选择最低成本执行计划。
mysql> explain
    -> select /*+ BKA(a, b) */ a.*
    -> from t1 a
    -> inner join t2 b
    -> on a.age=b.age
    -> ;
+----+-------------+-------+------------+------+---------------+--------+---------+------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key    | key_len | ref        | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+--------+---------+------------+------+----------+-------------+
|  1 | SIMPLE      | a     | NULL       | ALL  | ix_age        | NULL   | NULL    | NULL       |   10 |   100.00 | Using where |
|  1 | SIMPLE      | b     | NULL       | ref  | ix_age        | ix_age | 5       | modb.a.age |    2 |   100.00 | Using index |
+----+-------------+-------+------------+------+---------------+--------+---------+------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)

-- 禁用BKA,由于被驱动表索引失效,从Extra看出执行计划走了NLJ算法,性能很差
mysql> explain
    -> select /*+ NO_BKA(a, b) */ a.*
    -> from t1 a
    -> inner join t2 b
    -> on a.age=cast(b.age as char)
    -> ;
+----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type  | possible_keys | key    | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | b     | NULL       | index | NULL          | ix_age | 5       | NULL |   10 |   100.00 | Using index           |
|  1 | SIMPLE      | a     | NULL       | ref   | ix_age        | ix_age | 5       | func |    2 |   100.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+-----------------------+
2 rows in set, 1 warning (0.00 sec)

mysql> explain analyze
    -> select /*+ NO_BKA(a, b) */ a.*
    -> from t1 a
    -> inner join t2 b
    -> on a.age=cast(b.age as char)
    -> ;
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join  (cost=8.25 rows=20) (actual time=1.039..12.472 rows=20 loops=1)
    -> Covering index scan on b using ix_age  (cost=1.25 rows=10) (actual time=0.248..1.295 rows=10 loops=1)
    -> Index lookup on a using ix_age (age=cast(b.age as char charset utf8mb4)), with index condition: (cast(a.age as double) = cast(cast(b.age as char charset utf8mb4) as double))  (cost=0.52 rows=2) (actual time=0.659..0.847 rows=2 loops=10)
 |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.02 sec)

注意事项:

  • BKA提示相当于set optimizer_switch=‘batched_key_access=on’; 默认为关闭。
  • BKA提示开启时,优化器会考虑评估此算法的成本,但最终会选择最低成本的执行计划。
  • BKA、NO_BAK提示优先级高于optimizer_switch变量。

2. BNL, NO_BNL:启用或禁用BNL算法对表进行JOIN。

-- mysql5.7版本,由于没有hash算法,当被驱动表没有合适索引时,使用BNL算法
mysql> explain
    -> select /*+ BNL(a, b) */ a.*
    -> from t1 a
    -> inner join t2 b
    -> on a.name=b.name
    -> ;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                              |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
|  1 | SIMPLE      | a     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   10 |   100.00 | NULL                                               |
|  1 | SIMPLE      | b     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   10 |    10.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

-- mysql5.7版本,当使用NO_BNL提示时,禁用BNL算法,只能使用NLJ嵌套循环
mysql> explain
    -> select /*+ NO_BNL(a, b) */ a.*
    -> from t1 a
    -> inner join t2 b
    -> on a.name=b.name
    -> ;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | a     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   10 |   100.00 | NULL        |
|  1 | SIMPLE      | b     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   10 |    10.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)

-- mysql8.0版本,由于hash比BNL算法成本更低,故优化器选择hash算法
mysql> explain
    -> select /*+ BNL(a, b) */ a.*
    -> from t1 a
    -> inner join t2 b
    -> on a.name=b.name
    -> ;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                      |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
|  1 | SIMPLE      | a     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   10 |   100.00 | NULL                                       |
|  1 | SIMPLE      | b     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   10 |    10.00 | Using where; Using join buffer (hash join) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
2 rows in set, 1 warning (0.01 sec)

mysql> explain analyze
    -> select /*+ BNL(a, b) */ a.*
    -> from t1 a
    -> inner join t2 b
    -> on a.name=b.name
    -> ;
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                                                                                                  |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Inner hash join (b.`name` = a.`name`)  (cost=11.50 rows=10) (actual time=3.589..4.820 rows=18 loops=1)
    -> Table scan on b  (cost=0.04 rows=10) (actual time=0.448..0.826 rows=10 loops=1)
    -> Hash
        -> Table scan on a  (cost=1.25 rows=10) (actual time=0.446..0.842 rows=10 loops=1)
 |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

-- mysql8.0版本,当使用NO_BNL提示时,禁用hash算法,同时也禁用BNL算法
mysql> explain
    -> select /*+ NO_BNL(a, b) */ a.*
    -> from t1 a
    -> inner join t2 b
    -> on a.name=b.name
    -> ;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | a     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   10 |   100.00 | NULL        |
|  1 | SIMPLE      | b     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   10 |    10.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
2 rows in set, 1 warning (0.01 sec)

mysql> explain analyze
    -> select /*+ NO_BNL(a, b) */ a.*
    -> from t1 a
    -> inner join t2 b
    -> on a.name=b.name
    -> ;
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                                                                                                                                                                             |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join  (cost=13.75 rows=10) (actual time=1.216..16.920 rows=18 loops=1)
    -> Table scan on a  (cost=1.25 rows=10) (actual time=0.302..1.372 rows=10 loops=1)
    -> Filter: (b.`name` = a.`name`)  (cost=0.26 rows=1) (actual time=0.642..1.291 rows=2 loops=10)
        -> Table scan on b  (cost=0.26 rows=10) (actual time=0.278..0.772 rows=10 loops=10)
 |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.03 sec)

注意事项:

  • BNL提示相当于set optimizer_switch=‘block_nested_loop=on’; 默认为开启
  • BNL提示开启时,优化器会考虑评估此算法的成本,但最终会选择最低成本的执行计划。mysql8.0增加了HASH算法,它的成本一般比BNL算法成本更低。
  • BNL、NO_BNL提示优先级高于optimizer_switch变量。
  • BNL、NO_BNL提示可以启用/禁用HASH JOIN。

3. DERIVED_CONDITION_PUSHDOWN, NO_DERIVED_CONDITION_PUSHDOWN:启用或禁用派生表条件下推

-- 派生表条件下推默认开启,会考虑把子查询外层的条件推入到子查询内部
mysql> explain 
    -> SELECT /*+ DERIVED_CONDITION_PUSHDOWN() */ * FROM 
    -> (SELECT age, count(*) cnt FROM t1 GROUP BY age) AS dt 
    -> WHERE age > 50;
+----+-------------+------------+------------+-------+---------------+--------+---------+------+------+----------+--------------------------+
| id | select_type | table      | partitions | type  | possible_keys | key    | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+------------+------------+-------+---------------+--------+---------+------+------+----------+--------------------------+
|  1 | PRIMARY     | <derived2> | NULL       | ALL   | NULL          | NULL   | NULL    | NULL |    2 |   100.00 | NULL                     |
|  2 | DERIVED     | t1         | NULL       | range | ix_age        | ix_age | 5       | NULL |    1 |   100.00 | Using where; Using index |
+----+-------------+------------+------------+-------+---------------+--------+---------+------+------+----------+--------------------------+
2 rows in set, 1 warning (0.02 sec)

-- 从warning信息可以看出,SQL进行了改写
mysql> show warnings;
+-------+------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                                                                                |
+-------+------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select /*+ DERIVED_CONDITION_PUSHDOWN(@`select#1`) */ `dt`.`age` AS `age`,`dt`.`cnt` AS `cnt` from (/* select#2 */ select `modb`.`t1`.`age` AS `age`,count(0) AS `cnt` from `modb`.`t1` where (`modb`.`t1`.`age` > 50) group by `modb`.`t1`.`age`) `dt` |
+-------+------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

-- 从最后一行 using ix_age over (50 < age) 可看出,条件已推入到子查询内部
mysql> explain analyze
    -> SELECT * FROM 
    -> (SELECT age, count(*) cnt FROM t1 GROUP BY age) AS dt 
    -> WHERE age > 50;
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Table scan on dt  (cost=3.17..3.17 rows=1) (actual time=0.793..0.793 rows=0 loops=1)
    -> Materialize  (cost=0.66..0.66 rows=1) (actual time=0.610..0.610 rows=0 loops=1)
        -> Group aggregate: count(0)  (cost=0.56 rows=1) (actual time=0.451..0.451 rows=0 loops=1)
            -> Filter: (t1.age > 50)  (cost=0.46 rows=1) (actual time=0.351..0.351 rows=0 loops=1)
                -> Covering index range scan on t1 using ix_age over (50 < age)  (cost=0.46 rows=1) (actual time=0.251..0.251 rows=0 loops=1)
 |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

-- 禁止派生表条件下推,可以看出,子查询先物化,然后在外层过滤age条件
mysql> explain 
    -> SELECT /*+ NO_DERIVED_CONDITION_PUSHDOWN() */ * FROM 
    -> (SELECT age, count(*) cnt FROM t1 GROUP BY age) AS dt 
    -> WHERE age > 50;
+----+-------------+------------+------------+-------+---------------+--------+---------+------+------+----------+-------------+
| id | select_type | table      | partitions | type  | possible_keys | key    | key_len | ref  | rows | filtered | Extra       |
+----+-------------+------------+------------+-------+---------------+--------+---------+------+------+----------+-------------+
|  1 | PRIMARY     | <derived2> | NULL       | ALL   | NULL          | NULL   | NULL    | NULL |   10 |    33.33 | Using where |
|  2 | DERIVED     | t1         | NULL       | index | ix_age        | ix_age | 5       | NULL |   10 |   100.00 | Using index |
+----+-------------+------------+------------+-------+---------------+--------+---------+------+------+----------+-------------+
2 rows in set, 1 warning (0.01 sec)

-- 从第一行 Filter: (dt.age > 50) 可以看出,子查询先物化,最后才过滤条件
mysql> explain analyze
    -> SELECT /*+ NO_DERIVED_CONDITION_PUSHDOWN() */ * FROM 
    -> (SELECT age, count(*) cnt FROM t1 GROUP BY age) AS dt 
    -> WHERE age > 50;
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Filter: (dt.age > 50)  (cost=3.36..3.62 rows=3) (actual time=3.762..3.762 rows=0 loops=1)
    -> Table scan on dt  (cost=3.51..5.88 rows=10) (actual time=2.896..3.510 rows=5 loops=1)
        -> Materialize  (cost=3.25..3.25 rows=10) (actual time=2.706..2.706 rows=5 loops=1)
            -> Group aggregate: count(0)  (cost=2.25 rows=10) (actual time=0.735..2.097 rows=5 loops=1)
                -> Covering index scan on t1 using ix_age  (cost=1.25 rows=10) (actual time=0.487..1.241 rows=10 loops=1)
 |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

注意事项:

  • DERIVED_CONDITION_PUSHDOWN提示相当于set optimizer_switch=‘derived_condition_pushdown=on’; 默认为开启
  • DERIVED_CONDITION_PUSHDOWN提示开启时,优化器会考虑评估此算法的成本,但最终会选择最低成本的执行计划。
  • DERIVED_CONDITION_PUSHDOWN、NO_DERIVED_CONDITION_PUSHDOWN提示优先级高于optimizer_switch变量。
  • 派生表条件内推是常用的优化技巧,对子查询优化有较大的好处。

4. MERGE, NO_MERGE:启用或禁用视图合并(如视图、子查询、CTE等)

-- MERGE默认开启
mysql> explain SELECT /*+ MERGE(dt) */ * FROM (SELECT * FROM t1 where name='name1') AS dt;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   10 |    10.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

-- 从warning信息可以看出,优化器对SQL进行了改写,去掉了子查询
mysql> show warnings;
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                    |
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select /*+ MERGE(`dt`@`select#1`) */ `modb`.`t1`.`id` AS `id`,`modb`.`t1`.`name` AS `name`,`modb`.`t1`.`age` AS `age` from `modb`.`t1` where (`modb`.`t1`.`name` = 'name1') |
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

-- NO_MERGE提示,可以看出,子查询被物化
mysql> explain SELECT /*+ NO_MERGE(dt) */ * FROM (SELECT * FROM t1 where name='name1') AS dt;
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table      | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | PRIMARY     | <derived2> | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    2 |   100.00 | NULL        |
|  2 | DERIVED     | t1         | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   10 |    10.00 | Using where |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)

注意事项:

  • MERGE提示相当于set optimizer_switch=‘derived_merge=on’; 默认为开启
  • MERGE提示开启时,优化器会考虑评估此合并子查询的成本,但最终会选择最低成本的执行计划。
  • MERGE、NO_MERGE提示优先级高于optimizer_switch变量。
  • 对于NO_MERGE提示,会产生物化临时表。
  • 视图合并是常用的优化技巧,对子查询优化有较大的好处。
  • 对于视图引用,视图定义中的ALGORITHM={MERGE|TEMPTABLE}子句优先于引用该视图的查询中指定的提示。

知识总结

本文主要介绍了MySQL表级别Hints的使用方法,总结如下:

  1. 当优化器评估成本不符合我们的期望时,可以通过启用/禁用表级Hint,配合索引、数据倾斜等来优化SQL。
  2. 要了解这些算法底层意义,才能更好的利用它。
  3. 对于子查询优化,MySQL的优化能力目前为止有限,但可以通过DERIVED_CONDITION_PUSHDOWN、MERGE提示来优化(默认为开启)。如果达不到预期,则要通过改写SQL来进一步优化。

参考文档

https://dev.mysql.com/doc/refman/8.0/en/optimizer-hints.html#optimizer-hints-table-levelhttps://support.enmotech.com/article/3870/publish

举报

相关推荐

0 条评论