SQL 优化工具 Explain

本文详细介绍了MySQL的Explain命令,用于分析和优化SQL查询性能。通过Explain,可以查看查询的执行计划,包括表的加载顺序、查询类型、使用的索引等信息,帮助开发人员定位查询慢的问题。文章还探讨了Explain的各个字段含义,并提供了多个示例,展示了如何使用Explain来改进SQL查询效率。
摘要由CSDN通过智能技术生成

## 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查询优化器推算发现不走索引的成本更低,很可能就放弃索引了。

```

```

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值