zoukankan      html  css  js  c++  java
  • SQL优化:使用explain

    前文说了EXPLAIN的输出的含义,本文实战一下。

    Database Schema

    DROP DATABASE dbTest;
    CREATE DATABASE dbTest;
    USE dbTest;
    CREATE TABLE t1
    (
    c_primary_key               INT,
    c_unique_key                CHAR(64),
    c_unique_not_null_key       CHAR(64) NOT NULL,
    c_key                       CHAR(64),
    c_multi_key_part1           CHAR(64),
    c_multi_key_part2           CHAR(64),
    c_int_value                 INT,
    c_str_value                 CHAR(64),
    PRIMARY KEY(c_primary_key),
    UNIQUE KEY(c_unique_key),
    UNIQUE KEY(c_unique_not_null_key),
    KEY(c_multi_key_part1, c_multi_key_part2),
    KEY(c_key)
    )ENGINE=InnoDB;
    CREATE TABLE t2
    (
    c_primary_key               INT,
    c_unique_key                CHAR(64),
    c_unique_not_null_key       CHAR(64) NOT NULL,
    c_key                       CHAR(64),
    c_multi_key_part1           CHAR(64),
    c_multi_key_part2           CHAR(64),
    c_int_value                 INT,
    c_str_value                 CHAR(64),
    c_t1_primary_key            INT,
    PRIMARY KEY(c_primary_key),
    UNIQUE KEY(c_unique_key),
    UNIQUE KEY(c_unique_not_null_key),
    KEY(c_multi_key_part1, c_multi_key_part2),
    KEY(c_key),
    UNIQUE KEY(c_t1_primary_key)
    )ENGINE=InnoDB;
    

    Join类型

    const
    1. 使用primary key查找一条记录,满足const条件。

      mysql> EXPLAIN SELECT * FROM dbTest.t1 WHERE c_primary_key=1;
      +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
      | id | select_type | table | type  | possible_keys | key     | key_len | ref   | rows | Extra |
      +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
      |  1 | SIMPLE      | t1    | const | PRIMARY       | PRIMARY | 4       | const |    1 | NULL  |
      +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
      
    2. 使用unique key查找一条非NULL记录,满足const条件。

      mysql> EXPLAIN SELECT * FROM dbTest.t1 WHERE c_unique_key='4cb15758c8e311e5b46f06af68695f49';
      +----+-------------+-------+-------+---------------+--------------+---------+-------+------+-------+
      | id | select_type | table | type  | possible_keys | key          | key_len | ref   | rows | Extra |
      +----+-------------+-------+-------+---------------+--------------+---------+-------+------+-------+
      |  1 | SIMPLE      | t1    | const | c_unique_key  | c_unique_key | 65      | const |    1 | NULL  |
      +----+-------------+-------+-------+---------------+--------------+---------+-------+------+-------+
      
    3. 使用unique key查找NULL记录,这种情况下NULL记录可能有多条,所以不满足const条件。而是ref条件,ref意味着可能得到匹配的结果不唯一,即可能存在多条。那么对于unique key,可以为NULL,那么我们再来看:

      mysql> EXPLAIN SELECT * FROM dbTest.t1 WHERE c_unique_key is NULL;
      +----+-------------+-------+------+---------------+--------------+---------+-------+------+-----------------------+
      | id | select_type | table | type | possible_keys | key          | key_len | ref   | rows | Extra                 |
      +----+-------------+-------+------+---------------+--------------+---------+-------+------+-----------------------+
      |  1 | SIMPLE      | t1    | ref  | c_unique_key  | c_unique_key | 65      | const |    1 | Using index condition |
      +----+-------------+-------+------+---------------+--------------+---------+-------+------+-----------------------+
      
    4. 使用非NULL的unique key来查询,跟primary key类似,都是唯一的,所以满足const条件。

      mysql> EXPLAIN SELECT * FROM dbTest.t1 WHERE c_unique_not_null_key='00047412c96511e5844906af68695f49' limit 1;
      +----+-------------+-------+-------+-----------------------+-----------------------+---------+-------+------+-------+
      | id | select_type | table | type  | possible_keys         | key                   | key_len | ref   | rows | Extra |
      +----+-------------+-------+-------+-----------------------+-----------------------+---------+-------+------+-------+
      |  1 | SIMPLE      | t1    | const | c_unique_not_null_key | c_unique_not_null_key | 64      | const |    1 | NULL  |
      +----+-------------+-------+-------+-----------------------+-----------------------+---------+-------+------+-------+
      
    5. 使用非唯一的index列查询,可能存在多条记录,所以是ref而不是const。

      mysql> EXPLAIN SELECT * FROM dbTest.t1 WHERE c_key='4cb15758c8e311e5b46f06af68695f49';
      +----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------+
      | id | select_type | table | type | possible_keys | key   | key_len | ref   | rows | Extra                 |
      +----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------+
      |  1 | SIMPLE      | t1    | ref  | c_key         | c_key | 65      | const |    1 | Using index condition |
      +----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------+
      

      const至多有一条记录满足条件!

    eq_ref

    前文说,eq_ref类型对于之前表的每一个行组合,只从该表中读取一条记录。只有一条记录匹配要求,索引必须是primary key或者unique key(非NULL)。

    1. 使用primary key进行表关联

      mysql> EXPLAIN SELECT * FROM dbTest.t1,dbTest.t2 WHERE t1.c_primary_key=t2.c_key;
      +----+-------------+-------+--------+---------------+---------+---------+-----------------+------+-------------+
      | id | select_type | table | type   | possible_keys | key     | key_len | ref             | rows | Extra       |
      +----+-------------+-------+--------+---------------+---------+---------+-----------------+------+-------------+
      |  1 | SIMPLE      | t2    | ALL    | c_key         | NULL    | NULL    | NULL            | 8042 | Using where |
      |  1 | SIMPLE      | t1    | eq_ref | PRIMARY       | PRIMARY | 4       | dbTest.t2.c_key |    1 | Using where |
      +----+-------------+-------+--------+---------------+---------+---------+-----------------+------+-------------+
      

      可以看到,对于t2表中的每一行,t1中都有唯一的一行(至多一行)进行匹配,所以最终匹配为eq_ref。

    2. 使用NOT NULL的unique key(唯一的一行)进行关联

      mysql> EXPLAIN SELECT * FROM dbTest.t1,dbTest.t2 WHERE t1.c_unique_not_null_key=t2.c_unique_not_null_key;
      +----+-------------+-------+--------+-----------------------+-----------------------+---------+---------------------------------+------+-------+
      | id | select_type | table | type   | possible_keys         | key                   | key_len | ref                             | rows | Extra |
      +----+-------------+-------+--------+-----------------------+-----------------------+---------+---------------------------------+------+-------+
      |  1 | SIMPLE      | t1    | ALL    | c_unique_not_null_key | NULL                  | NULL    | NULL                            | 9307 | NULL  |
      |  1 | SIMPLE      | t2    | eq_ref | c_unique_not_null_key | c_unique_not_null_key | 64      | dbTest.t1.c_unique_not_null_key |    1 | NULL  |
      +----+-------------+-------+--------+-----------------------+-----------------------+---------+---------------------------------+------+-------+
      

      eq_ref至多有一条记录满足条件!

    ref

    对于之前表的每一个组合,匹配到索引值的所有记录将被读取。例如匹配那些左侧前缀的key(multi-part key),或者非primary key,或者非unique index(匹配值不是NULL),或者unique index(但是匹配值是NULL)。

    1. 使用index列匹配多行记录

      mysql> EXPLAIN SELECT * FROM t1 WHERE t1.c_key='58a95fbac96511e5844906';
      +----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------+
      | id | select_type | table | type | possible_keys | key   | key_len | ref   | rows | Extra                 |
      +----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------+
      |  1 | SIMPLE      | t1    | ref  | c_key         | c_key | 65      | const |    1 | Using index condition |
      +----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------+
      
    2. 多表关联时使用index列匹配多行记录

      mysql> EXPLAIN SELECT * FROM t1,t2 WHERE t1.c_unique_key=t2.c_key;
      +----+-------------+-------+------+---------------+-------+---------+------------------------+------+-------------+
      | id | select_type | table | type | possible_keys | key   | key_len | ref                    | rows | Extra       |
      +----+-------------+-------+------+---------------+-------+---------+------------------------+------+-------------+
      |  1 | SIMPLE      | t1    | ALL  | c_unique_key  | NULL  | NULL    | NULL                   | 9307 | Using where |
      |  1 | SIMPLE      | t2    | ref  | c_key         | c_key | 65      | dbTest.t1.c_unique_key |    1 | NULL        |
      +----+-------------+-------+------+---------------+-------+---------+------------------------+------+-------------+
      
    3. 匹配左侧前缀(t1.c_multi_key_part1)的多行记录

      mysql> EXPLAIN SELECT * FROM t1,t2 WHERE t1.c_multi_key_part1=t2.c_key;
      +----+-------------+-------+------+-------------------+-------------------+---------+-----------------+------+-------------+
      | id | select_type | table | type | possible_keys     | key               | key_len | ref             | rows | Extra       |
      +----+-------------+-------+------+-------------------+-------------------+---------+-----------------+------+-------------+
      |  1 | SIMPLE      | t2    | ALL  | c_key             | NULL              | NULL    | NULL            | 4904 | Using where |
      |  1 | SIMPLE      | t1    | ref  | c_multi_key_part1 | c_multi_key_part1 | 65      | dbTest.t2.c_key |    1 | NULL        |
      +----+-------------+-------+------+-------------------+-------------------+---------+-----------------+------+-------------+
      
    4. 使用unique index查找NULL的记录

      mysql> EXPLAIN SELECT * FROM t1 WHERE t1.c_unique_key is NULL;
      +----+-------------+-------+------+---------------+--------------+---------+-------+------+-----------------------+
      | id | select_type | table | type | possible_keys | key          | key_len | ref   | rows | Extra                 |
      +----+-------------+-------+------+---------------+--------------+---------+-------+------+-----------------------+
      |  1 | SIMPLE      | t1    | ref  | c_unique_key  | c_unique_key | 65      | const |    1 | Using index condition |
      +----+-------------+-------+------+---------------+--------------+---------+-------+------+-----------------------+
      

      ref可以匹配多行记录!

    ref_or_null

    从这个关键词可以看出,ref或者NULL,既在ref的基础上加上NULL的搜索。以下例子对应于ref的记录。

    1. 使用index列匹配多行记录,或者NULL记录

      mysql> EXPLAIN SELECT * FROM t1 WHERE t1.c_key='58a95fbac96511e5844906' or t1.c_key is NULL;
      +----+-------------+-------+-------------+---------------+-------+---------+-------+------+-----------------------+
      | id | select_type | table | type        | possible_keys | key   | key_len | ref   | rows | Extra                 |
      +----+-------------+-------+-------------+---------------+-------+---------+-------+------+-----------------------+
      |  1 | SIMPLE      | t1    | ref_or_null | c_key         | c_key | 65      | const |    2 | Using index condition |
      +----+-------------+-------+-------------+---------------+-------+---------+-------+------+-----------------------+
      
    2. 其余不再给输出,参照ref,SQL如下。

      mysql> EXPLAIN SELECT * FROM t1,t2 WHERE t1.c_unique_key=t2.c_key or t1.c_unique_key is NULL;
      mysql> EXPLAIN SELECT * FROM t1,t2 WHERE t1.c_multi_key_part1=t2.c_key or t1.c_multi_key_part1 is NULL;
      

      ref_or_null = ref + NULL记录,所以是多行

    range

    对于一个给定的range,使用index来获取记录。range可以使用=,<>,>,>=,BETWEEN,IN()等操作符。

    1. BETWEEN操作符

      mysql> EXPLAIN SELECT * FROM t1 WHERE t1.c_primary_key BETWEEN 10 AND 20;
      +----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
      | id | select_type | table | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
      +----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
      |  1 | SIMPLE      | t1    | range | PRIMARY       | PRIMARY | 4       | NULL |   10 | Using where |
      +----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
      
    2. IN操作符

      mysql> EXPLAIN SELECT * FROM t1 WHERE t1.c_primary_key IN (10, 20);
      +----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
      | id | select_type | table | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
      +----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
      |  1 | SIMPLE      | t1    | range | PRIMARY       | PRIMARY | 4       | NULL |    2 | Using where |
      +----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
      

    等等,不再赘述。

    range使用index,多条记录!

    index

    该类型跟ALL类型,但是不同之处在于搜索的是index数据,因为index数据比较小,所以效率肯定比ALL要高。分为两种情况:

    1. 索引数据足够满足要求,即索引数据包括了查询的所有数据(在InnoDB下,索引数据数据存储的内容,包括 索引列+主键列,如下,因为c_key索引数据包括了c_key列值+c_primary_key列值,所以只需遍历索引即可)

      mysql> EXPLAIN SELECT c_primary_key,c_key FROM t1;
      +----+-------------+-------+-------+---------------+-------+---------+------+------+-------------+
      | id | select_type | table | type  | possible_keys | key   | key_len | ref  | rows | Extra       |
      +----+-------------+-------+-------+---------------+-------+---------+------+------+-------------+
      |  1 | SIMPLE      | t1    | index | NULL          | c_key | 65      | NULL | 4915 | Using index |
      +----+-------------+-------+-------+---------------+-------+---------+------+------+-------------+
      
    2. 需要根据某个索引列的顺序进行查询,如下。第一条EXPLAIN从t1中select出所有的c_primary_key,这是不需要用到c_key(当然用c_key是也可以的)。但是第二条EXPLAIN由于需要按照c_key的进行排序(而c_key index就是有序的),所以只需要c_key index存储的顺序读取出来即可达到排序的功能,所以使用c_key就起到了排序的作用。

      mysql> EXPLAIN SELECT c_primary_key FROM t1;
      +----+-------------+-------+-------+---------------+-----------------------+---------+------+------+-------------+
      | id | select_type | table | type  | possible_keys | key                   | key_len | ref  | rows | Extra       |
      +----+-------------+-------+-------+---------------+-----------------------+---------+------+------+-------------+
      |  1 | SIMPLE      | t1    | index | NULL          | c_unique_not_null_key | 64      | NULL | 4915 | Using index |
      +----+-------------+-------+-------+---------------+-----------------------+---------+------+------+-------------+
      mysql> EXPLAIN SELECT c_primary_key FROM t1 order by c_key;
      +----+-------------+-------+-------+---------------+-------+---------+------+------+-------------+
      | id | select_type | table | type  | possible_keys | key   | key_len | ref  | rows | Extra       |
      +----+-------------+-------+-------+---------------+-------+---------+------+------+-------------+
      |  1 | SIMPLE      | t1    | index | NULL          | c_key | 65      | NULL | 4915 | Using index |
      +----+-------------+-------+-------+---------------+-------+---------+------+------+-------------+
      

      index这种情况也就比ALL要好一点,这种SQL需要重点review,以防带来灾难!

    ALL

    终于到了最差的情况,全表扫描。即没有合适的索引数据,所以只能一行一行的扫描数据了,沦落至此,可想而知效果极差。例如:

    1. 查询符合条件的记录,如下,由于c_str_value列没有索引,导致只能进行全表扫描。优化方法:可以在c_str_value列上加上索引。

      mysql> EXPLAIN SELECT * FROM t1 where t1.c_str_value='1111';
      +----+-------------+-------+------+---------------+------+---------+------+------+-------------+
      | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
      +----+-------------+-------+------+---------------+------+---------+------+------+-------------+
      |  1 | SIMPLE      | t1    | ALL  | NULL          | NULL | NULL    | NULL | 4915 | Using where |
      +----+-------------+-------+------+---------------+------+---------+------+------+-------------+
      
    2. 查询一个表的所有记录,如下。优化方法:在满足业务需求的情况下,把查询的所有列改成某几列,这样若是某个索引数据满足条件的话,可以不用遍历全表,而仅仅遍历索引数据即可。

      mysql> EXPLAIN SELECT * FROM t1;
      +----+-------------+-------+------+---------------+------+---------+------+------+-------+
      | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra |
      +----+-------------+-------+------+---------------+------+---------+------+------+-------+
      |  1 | SIMPLE      | t1    | ALL  | NULL          | NULL | NULL    | NULL | 4915 | NULL  |
      +----+-------------+-------+------+---------------+------+---------+------+------+-------+
      

      最慢的查询,必须得review这些SQL,数据量大的情况下,必然带来灾难!

    总结

    通过以上,我们可以看到效率排序为: const < eq_ref < ref < ref_or_null(range) < index < ALL,通常index和ALL是需要重点注意的。让我们的嗅觉灵敏起来吧。:)

    本着理论指导实践的原则,以上用实例对理论做了实践,难免出错,敬请指正。

    遗留问题:

    1. unique index如何保存NULL的索引?这个key允许多条NULL记录存在么?
    2. primary index可以为NULL么?
    3. index列中如何存储NULL数据?
  • 相关阅读:
    JavaScript基础概念之----作用域
    Vue-Router基础知识点总结【vue系列】
    前端如何进行seo优化
    常见算法
    ES6新特性
    VUE内使用AES(BCB)加解密
    VUE内使用RSA加解密
    vue 使用v-html指令渲染的富文本无法修改样式的解决方法
    js中字符串可以调用的方法
    基于H5的混合开发介绍(一)WebView
  • 原文地址:https://www.cnblogs.com/figo-cui/p/5177780.html
Copyright © 2011-2022 走看看