## SQL优化工具Explain
MySQL是关系型数据库中的一种,**查询功能强,数据一致性高,数据安全性高,支持二级索引**。但性能方面稍逊与非关系型数据库,特别是百万级别以上的数据,很容易出现查询慢的现象。这时候需要分析查询慢的原因,一般情况下是程序员SQL写的烂,或者是没有键索引,或者是索引失效等原因导致的。
这个时候MySQL提供的Explain命令就尤其重要,它可以对`SELECT`语句进行分析,并输出`SELECT`执行的详细信息,以供开发人员针对性优化。
使用方式在查询语句前面加上`Explain`:
```sql
Explain SELECT * FROM user;
```
结果如下:
```sql
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | user | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
```
## Explain有什么用
当`Explain`与`SQL`语句一起使用时,`MySQL`会显示 来自优化器关于SQL执行的信息。也就是说,MySQL解释了它将如何处理该查询语句,包括如何连接表以及什么顺序连接表等。
- 表的加载顺序
- `SQL`的查询类型
- 可能用到哪些索引,哪些索引又被实际使用
- 表与表之间的引用关系
- 一个表中有多少行被优化器查询...
## 如何查看MySQL优化器优化之后的SQL
```sql
# 先执行Explain
Explain select name from a;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | a | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
# 再执行
show warnings;
+-------+------+---------------------------------------------------------------------------+
| Level | Code | Message |
+-------+------+---------------------------------------------------------------------------+
| Note | 1003 | /* select#1 */ select `ljb_test`.`a`.`name` AS `name` from `ljb_test`.`a` |
+-------+------+---------------------------------------------------------------------------+
```
## Explain 有哪些信息
`Explain`执行计划包含字段信息有
`id`、`select_type`、`table`、`partitions`、`type`、`possible_keys`、`key`、`key_len`、`ref`、`rows`、`filtered`、`Extra`12个字段。
```sql
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
```
以下所有实例数据库版本为`MySQL8.0.23`:
```sql
mysql> select version() from dual;
+-----------+
| version() |
+-----------+
| 8.0.23 |
+-----------+
1 row in set (0.00 sec)
```
先创建三张表`a`、`b`、`c`:
```sql
create table a(
id int,
name varchar(12)
);
create table b(
id int,
name varchar(12)
);
create table c(
id int,
name varchar(12)
);
```
## Explain执行计划详解
### 一、Id
id:表示查询中执行select子句或者操作表的顺序,**id的值越大,代表优先级越高,越先执行。** id大致会出现3种情况:
#### 1. id相同
看到三条记录的id都相同,可以理解成这三个表为一组,具有同样的优先级,执行顺序由上而下,具体顺序由优化器决定:
```sql
mysql> Explain select * from a,b,c where a.id=b.id and b.id = c.id;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
| 1 | SIMPLE | a | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL |
| 1 | SIMPLE | b | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | Using where; Using join buffer (hash join) |
| 1 | SIMPLE | c | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | Using where; Using join buffer (hash join) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
```
#### 2. id 不同
如果我们的`SQL`中存在子查询,那么`id`的序号会递增,id值越大优先级越高越先被执行。当三个表依次嵌套,发现最里层的子查询id最大,最先执行。
```sql
mysql> Explain select * from a where a.id = (select id from b where b.id = (select id from c where c.id = 1));
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | PRIMARY | a | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | Using where |
| 2 | SUBQUERY | b | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | Using where |
| 3 | SUBQUERY | c | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
```
#### 3. 以上两种同时存在
将上边的`SQL`稍微修改一下,增加一个子查询,发现`id`的以上两种同时存在。相同`id`划分为一组,这样就有三组,同组的从上往下顺序执行,不同组id越大优先级越高,越先执行。
```sql
mysql> EXPLAIN select * from a a1 where a1.id = (select b1.id from b b1 where b1.id = (select c1.id from c c1 where c1.name='我是第三表2')) AND a1.id in(select id from a where a.name="我是第一表2");
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------------------------------------+
| 1 | PRIMARY | a1 | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | Using where |
| 1 | PRIMARY | a | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | Using where; FirstMatch(a1); Using join buffer (hash join) |
| 2 | SUBQUERY | b1 | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | Using where |
| 3 | SUBQUERY | c1 | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------------------------------------+
```
### 二、select_type
`select_type`:表示`select`查询的类型,主要是用于区分各种复杂的查询。例如:普通查询、联合查询、子查询等。
#### 1.SIMPLE
`Simple`:表示最简单的select查询语句,也就是在查询中不包含子查询或者`union`交集等操作。
#### 2.PRIMARY
`Primary`:当查询语句中包含任何复杂的子部分,最外层查询则被标记为`Primary`。
#### 3.SUBQUERY
`SubQuery`:当`select`或`where`列表中包含子查询,该子查询被标记为`SubQuery`。
#### 4.DERIVED
`Derived`:表示包含在from子句中的子查询的`select`,在我们的`from`列表中包含的子查询会被标记为`Derived`。
#### 5.UNION
`Union`:如果`union`后边又出现的`select` 语句,则会被标记为`union`;若 `union` 包含在 `from` 子句的子查询中,外层 `select` 将被标记为 `derived`。
#### 6.UNION RESULT
`Union Result`:代表从`union`的临时表中读取数据,而`table`列的`<union1,4>`表示用第一个和第四个`select`的结果进行`union`操作。
```sql
Explain SELECT
bb.name,
( SELECT id FROM a ) aa
FROM
( SELECT id, name FROM b WHERE name = '' ) bb UNION
( SELECT name, id FROM c );
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| 1 | PRIMARY | b | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | Using where |
| 2 | SUBQUERY | a | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL |
| 4 | UNION | c | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL |
|NULL| UNION RESULT | <union1,4> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
```
### 三、table
查询的表名,并不一定是真实存在的表,有别名显示别名,也可能为临时表,例如上面的`DERIVED`、`<union1,4>`等。
### 四、partitions
查询时匹配到的分区信息,对于非分区表值为NULL,当查询的是分区表时,`partitions`显示分区表命中的分区情况。
```sql
+----+-------------+----------------+---------------------------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------------+---------------------------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | a | p201801,p201802,p201803,p300012 | index | NULL | PRIMARY | 9 | NULL | 3 | 100 | Using index |
+----+-------------+----------------+---------------------------------+-------+---------------+---------+---------+------+------+----------+-------------+
```
### 五、type
`type`:查询使用了何种类型,它在`SQL`优化中是一个非常总要的指标,以下性能从好到坏依次是:
```sql
system > const > eq_ref > ref > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > all
```
#### 1.system
`system`:当表仅有一行记录时(系统表),数据量很少,往往不需要进行磁盘IO,速度非常快。
#### 2.const
`const`:表示查询时命中 `primary key` 主键或者 `unique` 唯一索引,或者被连接的部分是一个常量(const)值。这类扫描效率极高,返回数据量少,速度非常快。
```sql
# 为a表的id列添加唯一索引
alter table a add primary key(id);
Explain select id from a where id = 1;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | a | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
```
#### 3.eq_ref
`eq_ref`:查询时命中主键`primary key`或者`unique key`索引,`type`就是`eq_ref`。
```sql
# 为b表的id列添加唯一索引
alter table b add primary key(id);
Explain select a.name from a,b where a.id = b.id;
+----+-------------+-------+------------+--------+---------------+---------+---------+---------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------+---------+---------+---------------+------+----------+-------------+
| 1 | SIMPLE | a | NULL | ALL | PRIMARY | NULL | NULL | NULL | 1 | 100.00 | NULL |
| 1 | SIMPLE | b | NULL | eq_ref | PRIMARY | PRIMARY | 4 | ljb_test.a.id | 1 | 100.00 | Using index |
+----+-------------+-------+------------+--------+---------------+---------+---------+---------------+------+----------+-------------+
```
#### 4.ref
`ref`:区别于`eq_ref`,`ref`表示使用非唯一性索引,会找到很多个符合条件的行。
```sql
# 为a表的name列添加普通索引
alter table a add index index_name(name);
Explain select id from a where name = 'liu';
+----+-------------+-------+------------+------+---------------+------------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | a | NULL | ref | index_name | index_name | 51 | const | 1 | 100.00 | Using index |
+----+-------------+-------+------------+------+---------------+------------+---------+-------+------+----------+-------------+
```
#### 5.ref_or_null
`ref_or_null`:这种连接类型类似于 `ref`,区别在于 MySQL会额外搜索包含`NULL`值的行。
```sql
Explain select id from a where name = 'liu' or name is null;
+----+-------------+-------+------------+-------------+---------------+------------+---------+-------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------------+---------------+------------+---------+-------+------+----------+--------------------------+
| 1 | SIMPLE | a | NULL | ref_or_null | index_name | index_name | 51 | const | 2 | 100.00 | Using where; Using index |
+----+-------------+-------+------------+-------------+---------------+------------+---------+-------+------+----------+--------------------------+
```
#### 6.index_merge
`index_merge`:使用了索引合并优化方法,查询使用了两个以上的索引。
#### 7.unique_subquery
`unique_subquery`:替换下面的`IN`子查询,子查询返回不重复的集合。
```sql
value IN (SELECT primary_key FROM single_table WHERE some_expr)
```
#### 8.index_subquery
`index_subquery`:区别与`unique_subquery`,用于非唯一索引,可以返回重复值。
```sql
value IN (SELECT key_column FROM single_table WHERE some_expr)
```
#### 9.range
`range`:使用索引选择行,仅检索给定范围内的行。简单点说就是针对一个有索引的字段,给定范围检索数据。在`where`语句中使用 `bettween...and`、`<`、`>`、`<=`、`in` 等条件查询 type 都是 range。
举个栗子:`a`表中`id`为唯一主键,`age`普通字段未建索引。
```sql
Explain select * from a where id Between 2 and 3;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | a | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 1 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
```
从结果中看到只有对设置了索引的字段,做范围检索 `type` 才是 `range`。
```sql
Explain select * from a where age Between 2 and 3;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | a | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 50.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
```
#### 10、index
`index`:`Index` 与`ALL` 其实都是读全表,区别在于`index`是遍历索引树读取,而`ALL`是从硬盘中读取。
下面示例:`id` 为主键,不带 where 条件全表查询 ,`type`结果为`index` 。
```sql
Explain select id from a;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | a | NULL | index | NULL | PRIMARY | 4 | NULL | 2 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
```
#### 11、ALL
`ALL`:将遍历全表以找到匹配的行,性能最差。
```sql
Explain select * from a;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | a | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
```
### 六、possible_keys
`possible_keys`:表示在`MySQL`中通过哪些索引,能让我们在表中找到想要的记录,一旦查询涉及到的某个字段上存在索引,则索引将被列出,**但这个索引并不定一会是最终查询数据时所被用到的索引**。具体请参考上边的例子。
### 七、key
`key`:区别于`possible_keys`,key是查询中实际使用到的索引,若没有使用索引,显示为NULL。具体请参考上边的例子。
> 当 `type` 为 `index_merge` 时,可能会显示多个索引。
### 八、key_len
`key_len`:表示查询用到的索引长度(字节数),原则上长度越短越好 。
- 单列索引,那么需要将整个索引长度算进去。
- 多列索引,不是所有列都能用到,需要计算查询中实际用到的列。
> 注意:`key_len`只计算`where`条件中用到的索引长度,而排序和分组即便是用到了索引,也不会计算到`key_len`中。
### 九、ref
`ref`常见的有值:`const`、`func`、`null`,字段名。
### 十、rows
`rows`:以表的统计信息和索引使用情况,估算要找到我们所需的记录,需要读取的行数。
这是评估`SQL` 性能的一个比较重要的数据,`mysql`需要扫描的行数,很直观的显示 `SQL` 性能的好坏,一般情况下 `rows` 值越小越好。
```sql
Explain select * from a;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | a | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
select * from a;
+----+--------+------+
| id | name | age |
+----+--------+------+
| 1 | 知识 | NULL |
| 3 | liu | NULL |
+----+--------+------+
```
### 十一、filtered
`filtered`:这个是一个百分比的值,表里符合条件的记录数的百分比。简单点说,这个字段表示存储引擎返回的数据在经过过滤后,剩下满足条件的记录数量的比例。
在`MySQL.5.7`版本以前想要显示`filtered`需要使用`explain extended`命令。`MySQL.5.7`后,默认`explain`直接显示`partitions`和`filtered`的信息。
### 十二、Extra
`Extra` :是包含不适合在其它列显示但是十分重要的额外信息。`Explain` 中的很多额外的信息会在 `Extra` 字段显示。
#### 1、Using index
`Using index`:我们在相应的 select 操作中使用了覆盖索引,通俗一点讲就是查询的列被索引覆盖,使用到覆盖索引查询速度会非常快,SQL优化中理想的状态。
什么又是覆盖索引?
一条查询的 SQL只需要通过索引就可以返回,我们所需要查询的数据(一个或几个字段),而不必通过二级索引,查到主键之后再通过主键查询整行数据`(select * )`。
`id`表为主键
```sql
Explain select id from a;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | a | NULL | index | NULL | PRIMARY | 4 | NULL | 2 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
```
**注意**:想要使用到覆盖索引,我们在`select`时只取出需要的字段,不可`select *`,而且该字段建立了索引。
```sql
Explain select * from a;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | a | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
```
#### 2、Using where
`Using where`:查询时未找到可用的索引,进而通过`where`条件过滤获取所需数据,但要注意的是并不是所有带`where`语句的查询都会显示`Using where`。
下边示例中`name`列 并未用到索引,`type` 为 `ALL`,即MySQL通过全表扫描后再按`where`条件筛选数据。
```sql
Explain select name from a where name = 'liu';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | a | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 50.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
```
#### 3、Using temporary
`Using temporary`:表示查询后结果需要使用临时表来存储,一般在排序或者分组查询时用到。
```sql
Explain select name from a where id in (1,3) group by name;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+------------------------------+
| 1 | SIMPLE | a | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 2 | 100.00 | Using where; Using temporary |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+------------------------------+
```
#### 4、Using filesort
`Using filesort`:表示无法利用索引完成的排序操作,也就是`ORDER BY`的字段没有索引,通常这样的`SQL`都是需要优化的。
如果`ORDER BY`字段有索引就会用到覆盖索引,相比执行速度快很多。
#### 5、Using join buffer
`Using join buffer`:在我们联表查询的时候,如果表的连接条件没有用到索引,需要有一个连接缓冲区来存储中间结果。
```sql
# 添加索引
alter table a add index index_name(name);
alter table b add index index_name(name);
```
先看一下有索引的情况:连接条件 `a.name` 、`b.name` 都用到索引。
```sql
Explain select a.name from a ,b where a.name = b.name;
+----+-------------+-------+------------+-------+---------------+------------+---------+-----------------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+------------+---------+-----------------+------+----------+--------------------------+
| 1 | SIMPLE | b | NULL | index | index_name | index_name | 51 | NULL | 1 | 100.00 | Using where; Using index |
| 1 | SIMPLE | a | NULL | ref | index_name | index_name | 51 | ljb_test.b.name | 1 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+------------+---------+-----------------+------+----------+--------------------------+
```
接下来删除连接条件`a.name`、`b.name`的字段索引。发现Extra 列变成 `Using join buffer`,`type`均为全表扫描,这也是`SQL`优化中需要注意的地方。
```sql
# 删除索引
drop index index_name on a;
drop index index_name on b;
Explain select a.name from a ,b where a.name = b.name;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
| 1 | SIMPLE | b | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL |
| 1 | SIMPLE | a | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 50.00 | Using where; Using join buffer (hash join) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
```
#### 6、Impossible where
`Impossible where`:表示在我们用不太正确的`where`语句,导致没有符合条件的行。
```sql
Explain select name from a where 1=2;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+
| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Impossible WHERE |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+
```
### 7、No tables used
`No tables used`:我们的查询语句中没有`FROM`子句,或者有 `FROM DUAL`子句。
```sql
Explain select now();
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
```
`Extra`列的信息非常非常多,这里就不一一列举了,详见 [MySQL官方文档](https://dev.mysql.com/doc/refman/5.7/en/explain-output.html#jointype_index_merge)。
## 书写高质量SQL的一些建议
#### 查询SQL尽量不要使用select *,而是select具体字段。
反面例子:
```sql
select * from table_name;
```
正面例子:
```sql
select id,name from table_name;
```
理由:
- 只取需要的字段,节省资源、减少网络开销。
- select * 进行查询时,很可能就不会使用到覆盖索引了,就会造成回表查询。
#### 批量插入优化
反例:
```sql
INSERT into person(name,age) values('A',24);
INSERT into person(name,age) values('B',24);
INSERT into person(name,age) values('C',24);
```
正例:
```sql
INSERT into person(name,age) values('A',24),('B',24),('C',24);
```
#### like语句优化
```sql
select id from a where name like '%liu%';
```
由于`liu`前面用了`%`,会导致`name`列索引失效,因此该查询必然走全表查询。除非必要(模糊查询需要包含`liu`),否则不要在关键词前加`%`。
正例:
```sql
select id from a where name like 'liu%';
```
#### 尽量用 union all 替换 union
如果检索结果中不会有重复的记录,推荐union all 替换 union。
反例:
```sql
select * from user where userid=1
union
select * from user where age = 10
```
正例:
```sql
select * from user where userid=1
union all
select * from user where age = 10
```
理由:
- 如果使用union,不管检索结果有没有重复,都会尝试进行合并,然后在输出最终结果前进行排序。如果已知检索结果没有重复记录,使用union all 代替union,这样会提高效率。
#### 连接查询优化
`Inner join` 、`left join`、`right join`,优先使用`Inner join`,如果是`left join`,左边表结果尽量小。
> - Inner join 内连接,在两张表进行连接查询时,只保留两张表中完全匹配的结果集
- left join 在两张表进行连接查询时,会返回左表所有的行,即使在右表中没有匹配的记录。
- right join 在两张表进行连接查询时,会返回右表所有的行,即使在左表中没有匹配的记录。
都满足SQL需求的前提下,推荐优先使用Inner join(内连接),如果要使用left join,左边表数据结果尽量小,如果有条件的尽量放到左边处理。
理由:
- 如果inner join是等值连接,或许返回的行数比较少,所以性能相对会好一点。
- 同理,使用了左连接,左边表数据结果尽量小,条件尽量放到左边处理,意味着返回的行数可能比较少。
#### 尽量避免在 where后面使用!=或<>
在weher子句后面使用`!=`或`<>`操作符,会导致索引失效而进行全表扫描。
反例:
```sql
select age,name from user where age <>18;
```
正例:
```sql
//可以考虑分开两条sql写
select age,name from user where age <18;
select age,name from user where age >18;
```
#### 使用联合索引时,注意索引列的顺序,一般遵循最左匹配原则。
表结构:(有一个联合索引idx_userid_age,userId在前,age在后)
```sql
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`userId` int(11) NOT NULL,
`age` int(11) DEFAULT NULL,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_userid_age` (`userId`,`age`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
```
反例:
```sql
select * from user where age = 10;
```
正例:
```sql
# 符合最左匹配原则
select * from user where userid=10 and age =10;
# 符合最左匹配原则
select * from user where userid =10;
```
理由:
- 当我们创建一个联合索引的时候,如`(k1,k2,k3)`,相当于创建了`(k1)`、`(k1,k2)`和`(k1,k2,k3)`三个索引,这就是最左匹配原则。
- 联合索引不满足最左原则,索引一般会失效,但是这个还跟MySQL优化器有关的。
#### 不要有超过5个以上的表连接
- 连表越多,编译的时间和开销也就越大。
- 把连接表拆开成较小的几个执行,可读性更高。
- 如果一定需要连接很多表才能得到数据,那么意味着糟糕的设计了。
#### 索引不宜太多,一般5个以内。
- 索引并不是越多越好,索引虽然提高了查询的效率,但是也降低了插入和更新的效率。
- insert或update时有可能会重建索引,所以建索引需要慎重考虑,视具体情况来定。
- 一个表的索引数最好不要超过5个,若太多需要考虑一些索引是否没有存在的必要。
#### 索引不适合建在有大量重复数据的字段上,如性别这类型数据库字段
因为SQL优化器是根据表中数据量来进行查询优化的,如果索引列有大量重复数据,MySQL查询优化器推算发现不走索引的成本更低,很可能就放弃索引了。
```
```