MySQL执行计划

Explain或desc


SHOW WARNINGS; 这条语句可以查看MySQL执行的原生语句 而不是我们所编写的执行前的语句 MySQL5.6版本在执行计划中加入EXTENDED参数才能查看show warnings

  • ID 执行计划第一列

    • 单纯的join id 都是1且从上到下分析

      ref 出现在被驱动表中

      在没有索引的时候驱动表的数据会保存在join_buffer_size里面,然后在进行join

      所以执行join操作的时候,启动表要用小表

explain-id.png
  • subquery,scala subquery都会使id递增
explain-id2.png
subquery是from后面的子查询

scala subquery select和from之间的标量子查询
  • select_type

    • simple 不使用union或者subquery的简单query
select1.png
  • primary 使用union或者subquery的不能被合并的 query
select2.png
  • union 使用union结合的select除了第一个之外的select select_type用union

    union result是union去掉重复值的临时表

select3.png
union all不会出现union result 因为不去重
select4.png
  • subquery 这里的subquery是不使用在from后面的subquery

    注意点:跟外部表没啥关联 相当于一个常数字段

    执行计划没有错误 不代表SQL执行没有错误

select5.png
  • dependent subquery 必须依附于外面的值 如scala subquery或者exists

    1. scala subquery
select6.png
2. exists
select7.png
3. 两个都在就分不清了
  • derived(派生表)

    • From后面表的位置上的subquery

    • Derived是生成在内存或者临时表空间中的

    • 如果derived当作驱动表的时候 要点:要以减少数据量为目的

    • 当作被驱动表的时候产生auto_key索引也要以数据量为目的

    • 5.7 optimizer_switch='derived_merge=on' 可以把简单subquery打开成join

select8.png
- 5.7 optimizer_switch='derived_merge=on'影响和5.6升级5.7问题 
select9.png
select10.png

讨论derived_merge on/off对SQL优化有什么影响

  1. 为on的时候 被驱动表的连接条件需要索引
  2. 为off的时候 被驱动表结果集要小

- 5.6和5.7对于子查询的视图合并会去除order by

  例如如下语句

  ```mysql
  SELECT d.*,e.* FROM (SELECT * FROM t_group ORDER BY emp_no DESC) d JOIN employees e ON d.emp_no=e.emp_no
  ```

  5.6版本回执行子查询的order by 但是5.7及以上版本会视图合并 优化掉order by不执行排序 可使用limit等手段阻止视图合并

- Derived不能合并的几种情况

  1. union/union all
derived.png
  2. group
derived2.png
  3. distinct
derived3.png
  4. 聚合函数
derived4.png
  5. limit
derived5.png
  6. @(自定义变量)
derived6.png
     ```mysql
     WITH w1 AS ( SELECT t.*, 1 n FROM fm_station_detail t ORDER BY id LIMIT 100 ) SELECT
     id,
     fm_id,
     @m := @m + 1 m,
     CASE
        WHEN @de = fm_id THEN
        @row_num := @row_num + 1 ELSE @row_num := 1 
        END nm,
        @dense_rank := @dense_rank +
     CASE
            WHEN @de = fm_id THEN
            0 ELSE n 
        END dm,
        @de := fm_id d1 
     FROM
        w1,
     ( SELECT @m := 0, @row_num := 0, @dense_rank := 0, @de := "" ) b;
     ```
  • Materialized

    • 使用in的时候产生 也会产生auth_key(semi join的auth_key)索引 相当于
materialized.png
materialized2.png
materialized3.png
materialized4.png
  QB_NAME(name) 可以将子查询以块的形式命名

  ```mysql
  SELECT /*+ SEMIJOIN(@sub MATERIALIZATION) */ * FROM t_group t2 WHERE t2.emp_no IN 
  (SELECT /*+ QB_NAME(sub) */ t.emp_no FROM dept_emp t1);
  ```

  上述SQL表示将子查询命名为sub 并且强制以materialization的形式使用semi join进行查询
  • table

    • null 表示不使用任何表或者使用dual
table.png
  • 表名或者别名

  • 表示临时表 < >里面的数字表示对应的ID列

    如下图所示表示 id=2的临时表

    tmp_table_size=max_heap_table_size 适当的调整这个值得大小 可以调整性能

table2.png
同理
table3.png
  • type

    • const

      使用primary key或者unique获取数据

      特点是show warnings可以显示常数

type.png
  • eq_ref

    1. 必须是join
    2. 满足被驱动表的连接条件unique key或者primary key
type2.png
  • ref

    对普通索引列进行 where的等于查询 通常出现在联合索引部分使用的情况中 会产生性能问题

type3.png
可以通过使用force index() 方式解决

```mysql
SELECT * FROM salaries force index(PRIMARY) WHERE emp_no = 1001 AND from_date >= '1989-06-26';
```

原执行计划为ref 通过添加force index(PRIMARY) 可以将执行计划变为range 提升性能
type4.png
  • range

    索引范围查询 主要出现在 > , < ,between, in,like等范围查询中如下

type5.png
$\color{red}{range只能在驱动表或者被驱动表查询的驱动表 而不能直接用在被驱动表中 即range只能出现在驱动表中}$

$\color{red}{包含子查询的执行计划出现range表示这个子查询是单独进行的}$
type6.png
type7.png
  • index

    索引 全扫描 比起表全扫描且order by的请款下快

    但是绝大部分情况下是优化对象

    Extra 中出现use index说明没有回表 就说明整体性能还可以 整体的i/o小

    主要用于

    1. 不能使用range, const, ref
    2. 只查询索引列 即不回表
    3. 使用索引进行排序或者聚合 即省略排序

    满足联合索引中 前导列 没有在where条件且查询列在索引或者primary key中 包含emp_no index可用

type8.png
在聚合运算中group by后面的列在索引或者primary key中且查询列也在索引中index可用
type9.png
  • all

    全表扫描

    一般情况下是非常不好的执行计划 但是进行超过大表一半数据量的查询时 效果会更好 并且在整个执行计划中 all越靠后效率越低

    导致全表扫描的情况较多 没有索引 对索引列进行加工 索引列进行隐藏类型转换 对日期类型进行like 20% 单列索引的时候 对数字列进行like ‘30%’

    • 索引列进行了隐藏类型转换

      例如原字段是varchar类型 却使用number类型进行查询 就会导致索引失效造成全表扫描所以查询语句

type10.png
 $\color{red}{在执行join操作的时候 如果要使用被驱动表的索引 则驱动表的关联字段类型一定要与被驱动表的关联字段类型一致 字符型就都是字符型 数字型就都是数字型 如果不一样 则要对驱动表的关联字段进行类型转换}$

- 对数字列进行like ‘30%’
type11.png
  $\color{red}{P.S MySQL使用like进行查询时%在前不使用索引 如like ‘%冗余’ 而在后面使用%就会使用索引 如like ‘冗余%’ 这种情况只是针对字符类型才会出现 对于非字符类型的字段 无论%在前还是在后 都不会使用索引}$

- 对日期类型进行like 20%
type12.png
  acess 查询索引

  fitered 二次过滤

  对上面like ‘1986%’的执行计划分析如下

  $\color{red}{select_type是SIMPLE,说明没有子查询。 type为ref,说明使用了联合索引。 possible_keys是可能使用的索引,这里是PRIMARY,emp_no。key是用到的索引,这里只用到了PRIMARY。key_len表示使用的索引长度,这里只用到了4个长度。由下面的执行计划我们可以知道,联合索引被完全使用,key_len应该是7个长度,说明联合索引没有被完全使用。而且出现了filtered,值为11.11,说明发生了二次过滤,只保留了11.11%的结果集,二次过滤发生在回表之后,没有发生ICP。所以进一步说明使用like对非字符类型的字段进行查询,都不会使用索引。}$
  • possible_keys

    • 列出表所在的 索引名称
    • auto_key 5.6之后的版本开始提供auto_key这个功能
    • 所谓auto_key就是临时创建索引 需要消耗一些CPU和内存 对tmp_table_size,max_heap_table_size依赖较大
  • Key

    • 实际使用的索引名称 可以使用show index from table查询表的索引情况
key.png
上图中cardinality表示的是此索引列去除重复值后的行数
  • key_len

    • 可以通过key_len这个值判断在复合索引中具体使用了哪个索引

      可以通过下面的例子看出key_len的具体使用

keylen.png
  • rows

    • 是MySQL优化器根据统计信息预估出来的值 不准确
rows.png
  • filtered

    • 跟rows一样 是预估值 filtered非100的情况是extra有using where关键字 表示从innodb引擎中拿到数据后再加工的比率

      这个比率也是我们创建良好索引的依据

      MySQL5.7及以上版本通过condition_fanout_filter参数可以配置这个执行计划的显示和隐藏

filtered.png
  • Extra

    • Distinct

      MySQL在join过程中d中取出一行后 查询de表的时候 碰到一行就停止 有点像exists

      用Python表示 和下面代码的执行结果类似 当i=j后 则终止内循环

      a = [0,0,1,1,1,2,2,3,3,3,3]
      for i in range(4)
          for j in a
          print(i,j)
          if i == j
              break
      

      输出结果如下

      0 0
      1 0
      1 0
      1 1
      2 0
      2 0
      2 1
      2 1
      2 1
      2 2
      3 0
      3 0
      3 1
      3 1
      3 1
      3 2
      3 2
      3 3
      

      触发distinct的条件如下

      1. select 必须含有distinct关键字

      例如下面这个例子 select包含distinct关键字,但是由于驱动表是de,select中只包含d表的字段,所以extra中并没有触发distinct

extra1.png
使用straight_join hint强制使用d表成为驱动表优化后 则extra中出现distinct $\color{red}{P.S straight_join可以强制指定驱动表 straight_join左边的表一定是驱动表}$
extra2.png
  • Select tables optimized away

    触发条件

    1. 当select中只有min max count的时候出现
extra3.png
2. 复合主键(两个)的时候 使用其中的任意一个 只能用=号求出另一组且不能包含group by
extra4.png
  • Using filesort

    触发条件

    1. 在进行order by,group by且没使用索引的时候,order by一定会触发,group by在MySQL8.0版本之前会触发
extra5.png
   $\color{red}{P.S order by排序和sort_buffer_size参数有关 order by优先在内存中进行 当select的字段过多 数据集超过sort_buffer_size设置的大小时 整个排序就会在磁盘上进行 从而影响查询效率 所以select后面尽量不要跟随*}$
  • Using index

    触发条件

    1. 使用索引且不回表

      如果表对应的where条件选择率不好 且一行长度很长 则可以考虑创建包含对应列的索引 减少物理I/O达到优化目的

extra6.png
  • Using temporary

    MySQL执行过程中为了储存中间结果 会使用temporay table(临时表) 但是不能确定temporay table是生成在内存中还是磁盘中

    触发条件

    1. order by,group by,distinct没有使用索引
extra7.png
2. 执行计划中select_type为derved
extra8.png
3. show session status like ‘%tmp%’

以下两个重要的参数和生成临时表有关 一般是小的起作用 单位是字节
extra9.png
  • Using where

    一般using where和filtered,rows一起看 意思是产生了二次过滤

    using where表示从存储引擎中拿到一些数据 然后再过滤

    其中rows是在存储引擎中拿数据的预估值 filtered是再次过滤的百分比

extra10.png
  • Using index condition(icp 目标是减少回表数量)

    触发条件

    1. 必须是二级索引才有 且 有一部分索引无法使用的时候

    2. 必须开启icp。set session optimizer_switch='index_condition_pushdown=on'

      下面执行计划表示没有开启ICP之前的执行计划

extra11.png
   开启ICP之后
extra12.png
   重点如下

   $\color{red}{开启之前通过PRIMARY预先查询140w数据,因为只查询了数据表主键,所以需要使用主键回表查询140w全部数据,然后使用from_data字段进行like的二次过滤 二次过滤发生在回表之后}$

   $\color{red}{开启之后 使用emp_no联合索引查询主键emp_no和from_data两个字段的140w数据,然后通过from_data字段进行like的二次过滤 过滤之后在回表查询全部字段数据 回表数据是二次过滤后的结果集}$
  • Using MRR

    Mrr是二级索引获取pk之后 对pk进行排序来减少随机I/O 达到优化目的

    mrr 通过mrr_cost_based控制 set session optimizer_switch='mrr_cost_based=off'之后这可以使用mrr优化

    和ICP一样,只有发生回表才会出发

    \color{red}{在没有mrr的时候 查询结束之后,根据索引数据集回表查询全部字段数据,此时会发生随机I/O,影响效率。使用mmr后,查询结果集会使用PK进行排序,然后在回表,从而减少排序的随机I/O,达到优化的目的}

    使用PK进行排序占用空间的大小 由read_rnd_buffer_size控制 这个太小了 则不会产生mrr

extra13.png
  • Range checked for each record

    \color{red}{执行计划中一旦出现Range checked for each record,说明执行计划一定有问题,需要修改。出现这种情况一般是发生隐式转换或者完全没有使用索引的概率加大}

    有如下SQL

    SELECT 
      city,
      SUM(CASE WHEN op.payment_category IN (1,2,3,4,5,8,9,10) THEN op.payment_amount END) AS amount9,
      SUM(CASE WHEN op.payment_category IN (6,7,11) THEN op.payment_amount END) AS amount10
    FROM op
        LEFT JOIN o ON o.orders_no = op.orders_no
        LEFT JOIN ui ON ui.user_id = o.user_id
        LEFT JOIN qu ON ui.platform = qu.code
    WHERE 
        op.pqyment_category IN (1,2,3,4,5,6,7,8,9,10,11)
        AND qu.parent_code IN ('0001', '0002')
        AND op.create_time >= CONCAT(YEAR(DATE_FORMAT(NOW(), '%Y-%m-%d')), '-1-1 00:00:00')
        AND op.create_time <= CONCAT(YEAR(DATE_FORMAT(NOW(), '%Y-%m-%d')), '-12-31 23:59:59')
    

    执行计划如下

extra14.png
此时使用show warnings进行查看 发现存在隐式转换
extra15.png
  • Using join buffer (Block Nested Loop)

    set session optimizer_switch='block_nested_loop=on,batch_key_access=on'

    主要应用于被驱动表没有索引且数据量较少的时候,但大部分情况是优化对象

extra16.png
但是通过改变条件,导致索引失效,效率就会降低。如下图执行计划
extra17.png

几种优化器的相关参数


optimizer_switch.png
  • set session optimizer_switch='index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on'

    \color{red}{默认参数不需要改变,主要对一个表中含有多个索引的时候,在驱动表一次性使用两个以上的索引的时候触发。性能没有组合索引好,触发后要想办法去掉。所以在这些配置开启的时候,联合索引的效率要优于每一列的单个索引。}

  • materialization=on,semijoin=on,loosescan=on,firstmatch=on,duplicateweedout=on

    配置semi join相关参数,8.0.16版本之前都是对in的优化 16版本之后exists和in相同

    MariaDB有optimizer_switch里有exists_to_in的参数配置

    semijoin=on 控制semi join开关 总开关
    loosescan=on 驱动表 索引跳跃式查询
    firstmatch=on 被驱动表 索引跳跃式查询
    materialization=on 被驱动表auto_key开关
    duplicateweedout=on 最基本的semijoin去掉重复值

    \color{red}{当MySQL索引列的长度超过767后,则无法生成autokey 如果出现这种问题可以尝试使用convert函数修改成长度较短的字符集}

你可能感兴趣的:(MySQL执行计划)