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数据?
原文地址:https://www.cnblogs.com/figo-cui/p/5177780.html