Mysql 索引性能分析

逆流者 2021年04月04日 35次浏览

此篇为Mysql 学习笔记


由于mysql的版本不同,后面 explain 查询显示会不同,本博文采用 mysql5.5.54 windows 版本。
如使用的最新版mysql 5.7或者8版本,请参考官方文档。

MySQL常见瓶颈

CPU

  • SQL中对大量数据进行比较、关联、排序、分组
    最大的压力在于 比较

IO:

  • 实例内存满足不了缓存数据或排序等需要,导致产生大量 物理 IO。
  • 查询执行效率低,扫描过多数据行。

  • 不适宜的锁的设置,导致线程阻塞,性能下降。
  • 死锁,线程之间交叉调用资源,导致死锁,程序卡住。

服务器硬件的性能瓶颈:top,free, iostat和vmstat来查看系统的性能状态

Explain

使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理SQL语句的,分析查询语句或是表结构的性能瓶颈。

语法(Explain + SQL语句)

首先先准备一些建表语句,为使用explain做准备

 CREATE TABLE t1(id INT(10) AUTO_INCREMENT,content  VARCHAR(100) NULL ,  PRIMARY KEY (id));
 CREATE TABLE t2(id INT(10) AUTO_INCREMENT,content  VARCHAR(100) NULL ,  PRIMARY KEY (id));
 CREATE TABLE t3(id INT(10) AUTO_INCREMENT,content  VARCHAR(100) NULL ,  PRIMARY KEY (id));
 CREATE TABLE t4(id INT(10) AUTO_INCREMENT,content  VARCHAR(100) NULL ,  PRIMARY KEY (id));
 

INSERT INTO t1(content) VALUES(CONCAT('t1_',FLOOR(1+RAND()*1000)));

INSERT INTO t2(content) VALUES(CONCAT('t2_',FLOOR(1+RAND()*1000)));

INSERT INTO t3(content) VALUES(CONCAT('t3_',FLOOR(1+RAND()*1000)));

INSERT INTO t4(content) VALUES(CONCAT('t4_',FLOOR(1+RAND()*1000)));

结果:

mysql> SELECT * from t1;
+----+---------+
| id | content |
+----+---------+
|  1 | t1_20   |
+----+---------+
1 row in set (0.03 sec)

mysql> SELECT * from t2;
+----+---------+
| id | content |
+----+---------+
|  1 | t2_462  |
+----+---------+
1 row in set (0.04 sec)

mysql> SELECT * from t3;
+----+---------+
| id | content |
+----+---------+
|  1 | t3_247  |
+----+---------+
1 row in set (0.04 sec)

先执行一下:

mysql> EXPLAIN SELECT * from t1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set (0.02 sec)

看到执行结果有好多字段,下面逐个解释下。

id

select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序,一共有三种情况:

  • id相同,执行顺序由上至下
  • id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
  • id相同不同,同时存在

id相同

mysql> explain select * from t1,t2,t3 where t1.id = t2.id and t2.id = t3.id;
+----+-------------+-------+------------+--------+---------------+---------+---------+----------------+------+----------+-------+
| id | select_type | table | partitions | type   | possible_keys | key     | key_len | ref            | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------+---------+---------+----------------+------+----------+-------+
|  1 | SIMPLE      | t1    | NULL       | ALL    | PRIMARY       | NULL    | NULL    | NULL           |    1 |   100.00 | NULL  |
|  1 | SIMPLE      | t2    | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | index_db.t1.id |    1 |   100.00 | NULL  |
|  1 | SIMPLE      | t3    | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | index_db.t1.id |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+--------+---------------+---------+---------+----------------+------+----------+-------+
3 rows in set (0.03 sec)

id相同,执行顺序由上至下
此例中 先执行where 后的第一条语句 t1.id = t2.id 通过 t1.id 关联 t2.id 。 而 t2.id 的结果建立在t2.id=t3.id 的基础之上。

id不同

id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行

mysql> explain select t1.id from t1 where t1.id in (select t2.id from t2 where t2.id in (select t3.id from t3 where t3.content = ''));
+----+--------------------+-------+-----------------+---------------+---------+---------+------+------+--------------------------+
| id | select_type        | table | type            | possible_keys | key     | key_len | ref  | rows | Extra                    |
+----+--------------------+-------+-----------------+---------------+---------+---------+------+------+--------------------------+
|  1 | PRIMARY            | t1    | index           | NULL          | PRIMARY | 4       | NULL |    1 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | t2    | unique_subquery | PRIMARY       | PRIMARY | 4       | func |    1 | Using index; Using where |
|  3 | DEPENDENT SUBQUERY | t3    | unique_subquery | PRIMARY       | PRIMARY | 4       | func |    1 | Using where              |
+----+--------------------+-------+-----------------+---------------+---------+---------+------+------+--------------------------+
3 rows in set (0.05 sec)

id相同不同,同时存在

id如果相同,可以认为是一组,从上往下顺序执行;
在所有组中,id值越大,优先级越高,越先执行

mysql> explain select t2.* from t2, (select * from t3 where t3.content = '') s3 where s3.id = t2.id;
+----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra                                               |
+----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
|  1 | PRIMARY     | NULL  | NULL | NULL          | NULL | NULL    | NULL | NULL | Impossible WHERE noticed after reading const tables |
|  2 | DERIVED     | t3    | ALL  | NULL          | NULL | NULL    | NULL |    1 | Using where                                         |
+----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
2 rows in set

select_type

select_typeMeaning (含义)
STMPLESimple SELECT (not using UNION or subqueries)
简单的SELECT(不使用UNION或子查询)
PRIMARYOutermost SELECT
最外层的SELECT
UNIONSecond or later SELECT statement in a UNION
UNION中的第二个或更高版本的SELECT语句
DEPENDENT UNIONSecond or later SELECT statement in a UNION dependent on outer query
依赖于外部查询的UNION中的第二个或更高版本的SELECT语句
UNION RESULTResult of a UNION
UNION后的结果
SUBQUEEYFirst SELECT in subquery
子查询中的第一个SELECT
DEFENDENT SUBQUERYFirst SELECT in subquery, dependent on outer query
子查询中的第一个SELECT,取决于外部查询
DERIVEDDerived table SELECT (subquery in FROM clause)
派生表SELECT(FROM子句中的子查询)
UNCACHEABLE SUBQUERYA subquery for which the result cannot be cached and must bere-evaluated for each row of the outer query
子查询,其结果无法缓存,必须针对外部查询的每一行进行重新评估
UNCACHEABLE UNIONThe second or later select in a UNION that belongs to anuncacheable subquery(see UNCACHEABLE SUBQUERY)
UNION中属于不可缓存子查询的第二个或更高版本的选择(请参阅UNCACHEABLE子查询)

查询的类型,主要是用于区别 普通查询联合查询子查询等的复杂查询。

SIMPLE

简单的 select 查询,查询中不包含子查询或者UNION

mysql> explain select * from t1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set (0.04 sec)

PRIMARY

查询中若包含任何复杂的子部分,最外层查询则被标记为Primary

mysql> explain select * from (select t1.content from t1) a;
+----+-------------+------------+--------+---------------+------+---------+------+------+-------+
| id | select_type | table      | type   | possible_keys | key  | key_len | ref  | rows | Extra |
+----+-------------+------------+--------+---------------+------+---------+------+------+-------+
|  1 | PRIMARY     | <derived2> | system | NULL          | NULL | NULL    | NULL |    1 |       |
|  2 | DERIVED     | t1         | ALL    | NULL          | NULL | NULL    | NULL |    1 |       |
+----+-------------+------------+--------+---------------+------+---------+------+------+-------+
2 rows in set (0.04 sec)

DERIVED

在FROM列表中包含的子查询被标记为DERIVED(衍生) MySQL会递归执行这些子查询, 把结果放在临时表里。

mysql> explain select * from (select t1.content from t1) a;
+----+-------------+------------+--------+---------------+------+---------+------+------+-------+
| id | select_type | table      | type   | possible_keys | key  | key_len | ref  | rows | Extra |
+----+-------------+------------+--------+---------------+------+---------+------+------+-------+
|  1 | PRIMARY     | <derived2> | system | NULL          | NULL | NULL    | NULL |    1 |       |
|  2 | DERIVED     | t1         | ALL    | NULL          | NULL | NULL    | NULL |    1 |       |
+----+-------------+------------+--------+---------------+------+---------+------+------+-------+
2 rows in set (0.04 sec)

DERIVED 既查询通过子查询查出来的 临时表

SUBQUERY

在SELECT或WHERE列表中包含了子查询

mysql> explain select t2.id from t2 where t2.id = (select t3.id from t3 where t3.id = 2);
+----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra                                               |
+----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
|  1 | PRIMARY     | NULL  | NULL | NULL          | NULL | NULL    | NULL | NULL | Impossible WHERE noticed after reading const tables |
|  2 | SUBQUERY    | NULL  | NULL | NULL          | NULL | NULL    | NULL | NULL | no matching row in const table                      |
+----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
2 rows in set (0.05 sec)

DEPENDENT SUBQUERY

在SELECT或WHERE列表中包含了子查询,子查询基于外层

mysql> explain select t2.id from t2 where t2.id in (select t3.id from t3 where t3.content = '1');
+----+--------------------+-------+-----------------+---------------+---------+---------+------+------+--------------------------+
| id | select_type        | table | type            | possible_keys | key     | key_len | ref  | rows | Extra                    |
+----+--------------------+-------+-----------------+---------------+---------+---------+------+------+--------------------------+
|  1 | PRIMARY            | t2    | index           | NULL          | PRIMARY | 4       | NULL |    1 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | t3    | unique_subquery | PRIMARY       | PRIMARY | 4       | func |    1 | Using where              |
+----+--------------------+-------+-----------------+---------------+---------+---------+------+------+--------------------------+
2 rows in set

dependent subquery 与 subquery 的区别:

  • 依赖子查询 : 子查询结果为 多值
  • 子查询:查询结果为 单值

UNCACHEABLE SUBQUREY

无法被缓存的子查询

mysql> explain select * from t3 where id = (select id from t2 where t2.id = @@sort_buffer_size);
+----+----------------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
| id | select_type          | table | type | possible_keys | key  | key_len | ref  | rows | Extra                                               |
+----+----------------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
|  1 | PRIMARY              | NULL  | NULL | NULL          | NULL | NULL    | NULL | NULL | Impossible WHERE noticed after reading const tables |
|  2 | UNCACHEABLE SUBQUERY | NULL  | NULL | NULL          | NULL | NULL    | NULL | NULL | no matching row in const table                      |
+----+----------------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
2 rows in set (0.03 sec)

图中的 @@ 表示查的环境参数 ,没办法缓存。

UNION

若第二个SELECT出现在UNION之后,则被标记为UNION; 若UNION包含在FROM子句的子查询中,外层SELECT将被标记为:DERIVED

mysql> explain select t2.id,t2.content from t2 union all select t3.id,t3.content from t3;
+------+--------------+------------+------+---------------+------+---------+------+------+-------+
| id   | select_type  | table      | type | possible_keys | key  | key_len | ref  | rows | Extra |
+------+--------------+------------+------+---------------+------+---------+------+------+-------+
|    1 | PRIMARY      | t2         | ALL  | NULL          | NULL | NULL    | NULL |    1 |       |
|    2 | UNION        | t3         | ALL  | NULL          | NULL | NULL    | NULL |    1 |       |
| NULL | UNION RESULT | <union1,2> | ALL  | NULL          | NULL | NULL    | NULL | NULL |       |
+------+--------------+------------+------+---------------+------+---------+------+------+-------+
3 rows in set (0.07 sec)

UNION RESULT 两个语句执行完后的结果

UNION RESULT

从UNION表获取结果的SELECT

mysql> explain select t2.id,t2.content from t2 union all select t3.id,t3.content from t3;
+------+--------------+------------+------+---------------+------+---------+------+------+-------+
| id   | select_type  | table      | type | possible_keys | key  | key_len | ref  | rows | Extra |
+------+--------------+------------+------+---------------+------+---------+------+------+-------+
|    1 | PRIMARY      | t2         | ALL  | NULL          | NULL | NULL    | NULL |    1 |       |
|    2 | UNION        | t3         | ALL  | NULL          | NULL | NULL    | NULL |    1 |       |
| NULL | UNION RESULT | <union1,2> | ALL  | NULL          | NULL | NULL    | NULL | NULL |       |
+------+--------------+------------+------+---------------+------+---------+------+------+-------+
3 rows in set (0.07 sec)

table

显示这一行的数据是关于哪张表的

type

访问类型排列,显示查询使用了何种类型, 从最好到最差依次(常用)是: system>const>eq_ref>ref>range>index>ALL
|类型| 含义 |
|--|--|
| system|表只有一行记录(等于系统表),这是const类型的特列,平时不会出现,这个也可以忽略不计|
|const|表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快 如将主键置于where列表中,MySQL就能将该查询转换为一个常量|
|eq_ref|唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描|
| ref|非唯一性索引扫描,返回匹配某个单独值的所有行. 本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而, 它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体|
|range|只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引 一般就是在你的where语句中出现了between、<、>、in等的查询 这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束语另一点,不用扫描全部索引。|
|index|Full Index Scan,index与ALL区别为index类型只遍历索引树。这通常比ALL快,因为索引文件通常比数据文件小。 (也就是说虽然all和Index都是读全表,但index是从索引中读取的,而all是全表扫描读的)|
|all|Full Table Scan,将遍历全表以找到匹配的行|
|index_merge|在查询过程中需要多个索引组合使用,通常出现在有 or 的关键字的sql中|
|ref_or_null|对于某个字段既需要关联条件,也需要null值得情况下。查询优化器会选择用|ref_or_null连接查询。|
|index_subquery| 利用索引来关联子查询,不再全表扫描。|
|unique_subquery |该联接类型类似于index_subquery。 子查询中的唯一索引|

一般来说,得保证查询至少达到range级别,最好能达到ref。

const

表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快 如将主键置于where列表中,MySQL就能将该查询转换为一个常量。

mysql> explain select id from t1 where t1.id = 1;
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
| id | select_type | table | type  | possible_keys | key     | key_len | ref   | rows | Extra       |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
|  1 | SIMPLE      | t1    | const | PRIMARY       | PRIMARY | 4       | const |    1 | Using index |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
1 row in set (0.09 sec)

eq_ref

唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描。

mysql> explain select * from t1,t2 where t1.id = t2.id;
+----+-------------+-------+--------+---------------+---------+---------+----------------+------+-------+
| id | select_type | table | type   | possible_keys | key     | key_len | ref            | rows | Extra |
+----+-------------+-------+--------+---------------+---------+---------+----------------+------+-------+
|  1 | SIMPLE      | t1    | ALL    | PRIMARY       | NULL    | NULL    | NULL           |    1 |       |
|  1 | SIMPLE      | t2    | eq_ref | PRIMARY       | PRIMARY | 4       | index_db.t1.id |    1 |       |
+----+-------------+-------+--------+---------------+---------+---------+----------------+------+-------+
2 rows in set (0.11 sec)

ref

非唯一性索引扫描,返回匹配某个单独值的所有行. 本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而, 它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体。

mysql> explain select * from t1,t2 where t1.content = t2.content;
+----+-------------+-------+------+---------------+------+---------+------+------+--------------------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra                          |
+----+-------------+-------+------+---------------+------+---------+------+------+--------------------------------+
|  1 | SIMPLE      | t1    | ALL  | NULL          | NULL | NULL    | NULL |    1 |                                |
|  1 | SIMPLE      | t2    | ALL  | NULL          | NULL | NULL    | NULL |    1 | Using where; Using join buffer |
+----+-------------+-------+------+---------------+------+---------+------+------+--------------------------------+
2 rows in set (0.14 sec)
# 两张表的content字段没有索引,可以看到都是All(全表扫描)
mysql> create index index_content on t2(content);
Query OK, 0 rows affected (0.12 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> explain select * from t1,t2 where t1.content = t2.content;
+----+-------------+-------+------+---------------+---------------+---------+---------------------+------+--------------------------+
| id | select_type | table | type | possible_keys | key           | key_len | ref                 | rows | Extra                    |
+----+-------------+-------+------+---------------+---------------+---------+---------------------+------+--------------------------+
|  1 | SIMPLE      | t1    | ALL  | NULL          | NULL          | NULL    | NULL                |    1 |                          |
|  1 | SIMPLE      | t2    | ref  | index_content | index_content | 303     | index_db.t1.content |    1 | Using where; Using index |
+----+-------------+-------+------+---------------+---------------+---------+---------------------+------+--------------------------+
2 rows in set (0.11 sec)

t2表创建索引后,同样的语句t2表 达到了ref。

range

只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引 一般就是在你的where语句中出现了between、<、>、in等的查询 这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束语另一点,不用扫描全部索引。

mysql> explain select * from t1 where t1.id < 10;
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
|  1 | SIMPLE      | t1    | range | PRIMARY       | PRIMARY | 4       | NULL |    1 | Using where |
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
1 row in set (0.03 sec)

index

Full Index Scan,index与ALL区别为index类型只遍历索引树。这通常比ALL快,因为索引文件通常比数据文件小。 (也就是说虽然all和Index都是读全表,但index是从索引中读取的,而all是全表扫描读的)。

mysql> explain select id from t1;
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
|  1 | SIMPLE      | t1    | index | NULL          | PRIMARY | 4       | NULL |    1 | Using index |
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
1 row in set (0.04 sec)

all

Full Table Scan,将遍历全表以找到匹配的行。

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 |    1 |       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
1 row in set (0.09 sec)

index_merge

在查询过程中需要多个索引组合使用,通常出现在有 or 的关键字的sql中。

ref_or_null

对于某个字段既需要关联条件,也需要null值得情况下。查询优化器会选择用|ref_or_null连接查询。

mysql> explain select * from t2 where t2.content is null or t2.content = '1';
+----+-------------+-------+-------------+---------------+---------------+---------+-------+------+--------------------------+
| id | select_type | table | type        | possible_keys | key           | key_len | ref   | rows | Extra                    |
+----+-------------+-------+-------------+---------------+---------------+---------+-------+------+--------------------------+
|  1 | SIMPLE      | t2    | ref_or_null | index_content | index_content | 303     | const |    2 | Using where; Using index |
+----+-------------+-------+-------------+---------------+---------------+---------+-------+------+--------------------------+
1 row in set (0.07 sec)

index_subquery

利用索引来关联子查询,不再全表扫描。

mysql> explain select * from t2 where t2.content in (select t3.content from t3);
+----+--------------------+-------+-------+---------------+---------------+---------+------+------+--------------------------+
| id | select_type        | table | type  | possible_keys | key           | key_len | ref  | rows | Extra                    |
+----+--------------------+-------+-------+---------------+---------------+---------+------+------+--------------------------+
|  1 | PRIMARY            | t2    | index | NULL          | index_content | 303     | NULL |    1 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | t3    | ALL   | NULL          | NULL          | NULL    | NULL |    1 | Using where              |
+----+--------------------+-------+-------+---------------+---------------+---------+------+------+--------------------------+
2 rows in set (0.07 sec)

mysql> create index index_content on t3(content);
Query OK, 0 rows affected (0.22 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> explain select * from t2 where t2.content in (select t3.content from t3);
+----+--------------------+-------+----------------+---------------+---------------+---------+------+------+--------------------------+
| id | select_type        | table | type           | possible_keys | key           | key_len | ref  | rows | Extra                    |
+----+--------------------+-------+----------------+---------------+---------------+---------+------+------+--------------------------+
|  1 | PRIMARY            | t2    | index          | NULL          | index_content | 303     | NULL |    1 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | t3    | index_subquery | index_content | index_content | 303     | func |    1 | Using index; Using where |
+----+--------------------+-------+----------------+---------------+---------------+---------+------+------+--------------------------+
2 rows in set (0.08 sec)

unique_subquery

该联接类型类似于index_subquery。 子查询中的唯一索引。

mysql> explain select * from t2 where t2.id in (select t3.id from t3);
+----+--------------------+-------+-----------------+---------------+---------------+---------+------+------+--------------------------+
| id | select_type        | table | type            | possible_keys | key           | key_len | ref  | rows | Extra                    |
+----+--------------------+-------+-----------------+---------------+---------------+---------+------+------+--------------------------+
|  1 | PRIMARY            | t2    | index           | NULL          | index_content | 303     | NULL |    1 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | t3    | unique_subquery | PRIMARY       | PRIMARY       | 4       | func |    1 | Using index              |
+----+--------------------+-------+-----------------+---------------+---------------+---------+------+------+--------------------------+
2 rows in set (0.04 sec)

possible_keys

显示可能应用在这张表中的索引,一个或多个。 查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。

key

  • 实际使用的索引。如果为NULL,则没有使用索引
  • 查询中若使用了覆盖索引,则该索引和查询的select字段重叠

当查询具体某一字段时,且那个字段有索引时,key 值会显示为索引。

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 |    1 |       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
1 row in set (0.03 sec)

mysql> explain select id from t1;
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
|  1 | SIMPLE      | t1    | index | NULL          | PRIMARY | 4       | NULL |    1 | Using index |
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
1 row in set (0.03 sec)

key_len

  • 表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。
  • key_len字段能够帮你检查是否充分的利用上了索引。
CREATE TABLE t5 (
	id INT ( 10 ) AUTO_INCREMENT,
	a VARCHAR ( 100 ) NULL,
	b VARCHAR ( 100 ) NULL,
	c VARCHAR ( 100 ) NULL,
	d VARCHAR ( 100 ) NULL,
	PRIMARY KEY ( id )
);

insert into t5(a,b,c,d) values('a1','b1','c1','d1');
insert into t5(a,b,c,d) values('a2','b2','c2','d2');

create index index_a_b_c on t5(a,b,c);

mysql> show index from t5;
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name    | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| t5    |          0 | PRIMARY     |            1 | id          | A         |           2 | NULL     | NULL   |      | BTREE      |         |               |
| t5    |          1 | index_a_b_c |            1 | a           | A         |           2 | NULL     | NULL   | YES  | BTREE      |         |               |
| t5    |          1 | index_a_b_c |            2 | b           | A         |           2 | NULL     | NULL   | YES  | BTREE      |         |               |
| t5    |          1 | index_a_b_c |            3 | c           | A         |           2 | NULL     | NULL   | YES  | BTREE      |         |               |
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
4 rows in set (0.05 sec)
mysql> explain select * from t5;
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
|  1 | SIMPLE      | t5    | ALL  | NULL          | NULL | NULL    | NULL |    2 |       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
1 row in set (0.11 sec)

mysql> explain select * from t5 where t5.a = 'a1';
+----+-------------+-------+------+---------------+-------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key         | key_len | ref   | rows | Extra       |
+----+-------------+-------+------+---------------+-------------+---------+-------+------+-------------+
|  1 | SIMPLE      | t5    | ref  | index_a_b_c   | index_a_b_c | 303     | const |    1 | Using where |
+----+-------------+-------+------+---------------+-------------+---------+-------+------+-------------+
1 row in set (0.06 sec)

mysql> explain select * from t5 where t5.a = 'a1' and t5.b = 'b1';
+----+-------------+-------+------+---------------+-------------+---------+-------------+------+-------------+
| id | select_type | table | type | possible_keys | key         | key_len | ref         | rows | Extra       |
+----+-------------+-------+------+---------------+-------------+---------+-------------+------+-------------+
|  1 | SIMPLE      | t5    | ref  | index_a_b_c   | index_a_b_c | 606     | const,const |    1 | Using where |
+----+-------------+-------+------+---------------+-------------+---------+-------------+------+-------------+
1 row in set (0.06 sec)

mysql> explain select * from t5 where t5.a = 'a1' and t5.b = 'b1' and t5.c = 'c1';
+----+-------------+-------+------+---------------+-------------+---------+-------------------+------+-------------+
| id | select_type | table | type | possible_keys | key         | key_len | ref               | rows | Extra       |
+----+-------------+-------+------+---------------+-------------+---------+-------------------+------+-------------+
|  1 | SIMPLE      | t5    | ref  | index_a_b_c   | index_a_b_c | 909     | const,const,const |    1 | Using where |
+----+-------------+-------+------+---------------+-------------+---------+-------------------+------+-------------+
1 row in set (0.12 sec)

ref

显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值

mysql> explain select * from t5 where t5.a = 'a1' and t5.b = 'b1' and t5.c = 'c1';
+----+-------------+-------+------+---------------+-------------+---------+-------------------+------+-------------+
| id | select_type | table | type | possible_keys | key         | key_len | ref               | rows | Extra       |
+----+-------------+-------+------+---------------+-------------+---------+-------------------+------+-------------+
|  1 | SIMPLE      | t5    | ref  | index_a_b_c   | index_a_b_c | 909     | const,const,const |    1 | Using where |
+----+-------------+-------+------+---------------+-------------+---------+-------------------+------+-------------+
1 row in set (0.12 sec)

rows

  • rows列显示MySQL认为它执行查询时必须检查的行数。
  • 越少越好
mysql> explain select * from t5 where t5.a = 'a1';
+----+-------------+-------+------+---------------+-------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key         | key_len | ref   | rows | Extra       |
+----+-------------+-------+------+---------------+-------------+---------+-------+------+-------------+
|  1 | SIMPLE      | t5    | ref  | index_a_b_c   | index_a_b_c | 303     | const |    1 | Using where |
+----+-------------+-------+------+---------------+-------------+---------+-------+------+-------------+
1 row in set (0.02 sec)

mysql> explain select * from t5 where t5.d = 'd1';
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | t5    | ALL  | NULL          | NULL | NULL    | NULL |    2 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.03 sec)

Extra

包含不适合在其他列中显示但十分重要的额外信息。
|类型| 说明 |
|--|--|
| Using filesort | 说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。 MySQL中无法利用索引完成的排序操作称为“文件排序”|
| Using temporary | 使了用临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序 order by 和分组查询 group by。|
| USING index| 表示相应的select操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错! 如果同时出现using where,表明索引被用来执行索引键值的查找; 如果没有同时出现using where,表明索引只是用来读取数据而非利用索引执行查找。|
| Using where| 表明使用了where过滤|
| using join buffer| 使用了连接缓存:|
| impossible where| where子句的值总是false,不能用来获取任何元组|
| select tables optimized away| 在没有GROUPBY子句的情况下,基于索引优化MIN/MAX操作或者 对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计算, 查询执行计划生成的阶段即完成优化。|

单独解释下什么是覆盖索引(Covering Index):

索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据;当能通过读取索引就可以得到想要的数据,那就不需要读取行了。

一个索引包含了(或覆盖了)查询的字段[select子句]与查询条件[Where子句]中所有需要的字段就叫做覆盖索引

select id , name from t_xxx where age=18;

有一个组合索引 idx_id_name_age_xxx 包含了(覆盖了),id,name,age三个字段。查询时直接将建立了索引的列读取出来了,而不需要去查找所在行的其他数据。所以很高效。
在数据量较大,固定字段查询情况多时可以使用这种方法。

注意:

  • 如果要使用覆盖索引,一定要注意select列表中只取出需要的列,不可select *,因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降。

Using filesort

说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。 MySQL中无法利用索引完成的排序操作称为“文件排序”。

mysql> explain select * from t5 where t5.a = 'a1' order by d;
+----+-------------+-------+------+---------------+-------------+---------+-------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key         | key_len | ref   | rows | Extra                       |
+----+-------------+-------+------+---------------+-------------+---------+-------+------+-----------------------------+
|  1 | SIMPLE      | t5    | ref  | index_a_b_c   | index_a_b_c | 303     | const |    1 | Using where; Using filesort |
+----+-------------+-------+------+---------------+-------------+---------+-------+------+-----------------------------+
1 row in set (0.09 sec)

给d字段加上索引就不会出现 Using filesort了

Using temporary

使了用临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序 order by 和分组查询 group by。

mysql> explain select * from t5 where t5.a = 'a1' group by d;
+----+-------------+-------+------+---------------+-------------+---------+-------+------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key         | key_len | ref   | rows | Extra                                        |
+----+-------------+-------+------+---------------+-------------+---------+-------+------+----------------------------------------------+
|  1 | SIMPLE      | t5    | ref  | index_a_b_c   | index_a_b_c | 303     | const |    1 | Using where; Using temporary; Using filesort |
+----+-------------+-------+------+---------------+-------------+---------+-------+------+----------------------------------------------+
1 row in set (0.14 sec)

USING index

表示相应的select操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错! 如果同时出现using where,表明索引被用来执行索引键值的查找; 如果没有同时出现using where,表明索引只是用来读取数据而非利用索引执行查找。

mysql> show index from t5;
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name    | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| t5    |          0 | PRIMARY     |            1 | id          | A         |           2 | NULL     | NULL   |      | BTREE      |         |               |
| t5    |          1 | index_a_b_c |            1 | a           | A         |           2 | NULL     | NULL   | YES  | BTREE      |         |               |
| t5    |          1 | index_a_b_c |            2 | b           | A         |           2 | NULL     | NULL   | YES  | BTREE      |         |               |
| t5    |          1 | index_a_b_c |            3 | c           | A         |           2 | NULL     | NULL   | YES  | BTREE      |         |               |
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
4 rows in set (0.09 sec)

mysql> explain select a,b from t5 where t5.c = 'c1';
+----+-------------+-------+-------+---------------+-------------+---------+------+------+--------------------------+
| id | select_type | table | type  | possible_keys | key         | key_len | ref  | rows | Extra                    |
+----+-------------+-------+-------+---------------+-------------+---------+------+------+--------------------------+
|  1 | SIMPLE      | t5    | index | NULL          | index_a_b_c | 909     | NULL |    2 | Using where; Using index |
+----+-------------+-------+-------+---------------+-------------+---------+------+------+--------------------------+
1 row in set (0.07 sec)

mysql> explain select a,b,d from t5 where t5.c = 'c1';
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | t5    | ALL  | NULL          | NULL | NULL    | NULL |    2 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.07 sec)

看上面执行结果有没有使用到覆盖索引(Using index)。

Using where

表明使用了where过滤

using join buffer

使用了连接缓存:

CREATE TABLE `t_dept` (
 `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT "主键",
 `dept_code` VARCHAR(40) DEFAULT NULL COMMENT "部门代码",
 `dept_name` VARCHAR(40) DEFAULT NULL COMMENT "部门名称",
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1;
 
CREATE TABLE `t_emp` (
 `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT "主键",
 `name` VARCHAR(20) DEFAULT NULL COMMENT "员工名称",
  `age` INT(3) DEFAULT NULL COMMENT "年龄",
 `dept_id` INT(11) DEFAULT NULL COMMENT "部门表id",
 PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1;
 
 
 
INSERT INTO t_dept(dept_code, dept_name) VALUES('ceo','总裁部门');
INSERT INTO t_dept(dept_code, dept_name) VALUES('development','开发部门');
INSERT INTO t_dept(dept_code, dept_name) VALUES('test','测试部门');
 
 
INSERT INTO t_emp(NAME,age,dept_id) VALUES('小明', 25, 2);
INSERT INTO t_emp(NAME,age,dept_id) VALUES('小红', 23, 3);
INSERT INTO t_emp(NAME,age,dept_id) VALUES('小王', 35, null);

mysql> explain select * from t_emp,t_dept where t_emp.dept_id = t_dept.id;
+----+-------------+--------+------+---------------+------+---------+------+------+--------------------------------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows | Extra                          |
+----+-------------+--------+------+---------------+------+---------+------+------+--------------------------------+
|  1 | SIMPLE      | t_emp  | ALL  | NULL          | NULL | NULL    | NULL |    3 |                                |
|  1 | SIMPLE      | t_dept | ALL  | PRIMARY       | NULL | NULL    | NULL |    3 | Using where; Using join buffer |
+----+-------------+--------+------+---------------+------+---------+------+------+--------------------------------+
2 rows in set (0.09 sec)

impossible where

where子句的值总是false,不能用来获取任何数据

mysql> explain select * from t5 where t5.d = '1' and t5.d = '2';
+----+-------------+-------+------+---------------+------+---------+------+------+------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra            |
+----+-------------+-------+------+---------------+------+---------+------+------+------------------+
|  1 | SIMPLE      | NULL  | NULL | NULL          | NULL | NULL    | NULL | NULL | Impossible WHERE |
+----+-------------+-------+------+---------------+------+---------+------+------+------------------+
1 row in set (0.16 sec)

select tables optimized away

在没有GROUPBY子句的情况下,基于索引优化MIN/MAX操作或者 对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计算, 查询执行计划生成的阶段即完成优化。

CREATE TABLE user1 (
	id INT ( 10 ),
	name VARCHAR(20)
) ENGINE=INNODB;

INSERT INTO user1(id,name) VALUES(1, '小明');

CREATE TABLE user2 (
	id INT ( 10 ),
	name VARCHAR(20)
) ENGINE=MYISAM;

INSERT INTO user2(id,name) VALUES(1, '小明');
# INNODB
mysql> explain select count(*) from user1;
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
|  1 | SIMPLE      | user1 | ALL  | NULL          | NULL | NULL    | NULL |    1 |       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
1 row in set (0.02 sec)

# MYISAM
mysql> explain select count(*) from user2;
+----+-------------+-------+------+---------------+------+---------+------+------+------------------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra                        |
+----+-------------+-------+------+---------------+------+---------+------+------+------------------------------+
|  1 | SIMPLE      | NULL  | NULL | NULL          | NULL | NULL    | NULL | NULL | Select tables optimized away |
+----+-------------+-------+------+---------------+------+---------+------+------+------------------------------+
1 row in set (0.06 sec)

myisam 中会维护 总行数 (还有其他参数)这个参数,所以在执行查询时不会进行全表扫描。而是直接读取这个数。但会对增删产生一定的影响。根据业务情况决定谁好谁坏

innodb 中没有这个机制。

explain 能做什么

  • 表的读取顺序
  • 哪些索引可以使用
  • 数据读取操作的操作类型
  • 哪些索引被实际使用
  • 表之间的引用
  • 每张表有多少行被优化器查询