MySql数据库优化第二篇:通过EXPLAIN分析低效SQL的执行计划

   第一部分:在通过慢日志查询和show processlist命令查询到执行效率低的SQL语句后,可以通过EXPLAIN或者DESC命令,获取MySQL如何执行SELECT语句信息,包括在SELECT语句执行过程中表如何连接和连接的顺序;

 比如想统计email为租赁电影拷贝所支付的总金额,需要关联客户表customer和付款表payment,并且对付款金额amount字段做求和(sum)操作,相应的SQL的执行计划如下:

mysql> explain select sum(amount) from customer a,payment b where 1=1 and a.customer_id=b.customer_id
    -> and email ='[email protected]';


+----+-------------+-------+------+--------------------+--------------------+---------+----------------------+------+-------------+
| id | select_type | table | type | possible_keys      | key                | key_len | ref                  | rows | Extra       |
+----+-------------+-------+------+--------------------+--------------------+---------+----------------------+------+-------------+
|  1 | SIMPLE      | a     | ALL  | PRIMARY            | NULL               | NULL    | NULL                 |  646 | Using where |
|  1 | SIMPLE      | b     | ref  | idx_fk_customer_id | idx_fk_customer_id | 2       | sakila.a.customer_id |   11 |             |
+----+-------------+-------+------+--------------------+--------------------+---------+----------------------+------+-------------+

对每个列进行一些简单的说明:

  • select_type: 表示SELECT的类型,常见的取值有SIMPLE(简单表:既不是用表连接或者子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION中的第二个或者后面的查询语句)、SUBQUERY(子查询中的第一个SELECT)等。
  • table:输出结果集的表
  • possible_keys:表示查询时可能使用的索引
  • key:表示实际使用的索引
  • key_len:使用到索引字段的长度
  • rows: 扫描行的数量
  • Extra: 执行情况的说明和描述,包含不适合在其他列中显示,但是对执行计划非常重要的额外信息。
  • bype:表示MySQL在表中找到所需行的方式,或者叫访问类型,常见的类型如下:
ALL index range ref eq_ref const,system NULL

从左到右,性能由最差到最好。

(1)type=ALL,全表的扫描,MySQL遍历全表来找到匹配的行:

mysql> explain select * from film where rating>9;
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | film  | ALL  | NULL          | NULL | NULL    | NULL |    1 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+

(2)type=index,索引全扫描,MySQL遍历整个索引来查询匹配的行:

mysql> explain select title from film;
+----+-------------+-------+-------+---------------+-----------+---------+------+------+-------------+
| id | select_type | table | type  | possible_keys | key       | key_len | ref  | rows | Extra       |
+----+-------------+-------+-------+---------------+-----------+---------+------+------+-------------+
|  1 | SIMPLE      | film  | index | NULL          | idx_title | 767     | NULL |    1 | Using index |
+----+-------------+-------+-------+---------------+-----------+---------+------+------+-------------+

(3)type=range ,索引扫描范围,常见于< 、<=、>、>=、between等操作符;

mysql> explain select * from payment where customer_id>=300 and customer_id<=350;
+----+-------------+---------+-------+--------------------+--------------------+---------+------+------+-------------+
| id | select_type | table   | type  | possible_keys      | key                | key_len | ref  | rows | Extra       |
+----+-------------+---------+-------+--------------------+--------------------+---------+------+------+-------------+
|  1 | SIMPLE      | payment | range | idx_fk_customer_id | idx_fk_customer_id | 2       | NULL | 1349 | Using where |
+----+-------------+---------+-------+--------------------+--------------------+---------+------+------+-------------+

(4)type=ref ,使用非唯一索引扫描或唯一索引的前缀扫描,返回匹配某个单独值的记录行,例如:

mysql> explain select * from payment where customer_id =350;
+----+-------------+---------+------+--------------------+--------------------+---------+-------+------+-------+
| id | select_type | table   | type | possible_keys      | key                | key_len | ref   | rows | Extra |
+----+-------------+---------+------+--------------------+--------------------+---------+-------+------+-------+
|  1 | SIMPLE      | payment | ref  | idx_fk_customer_id | idx_fk_customer_id | 2       | const |   23 |       |
+----+-------------+---------+------+--------------------+--------------------+---------+-------+------+-------+

        索引 idx_fk_customer_id 是非唯一索引,查询条件为等值查询条件customer_id= 35,所以扫描索引的类型为ref 。ref还经常出现在join操作中:

mysql> explain select b.*,a.* from payment a,customer b where a.customer_id =b.customer_id;
+----+-------------+-------+------+--------------------+--------------------+---------+----------------------+------+-------+
| id | select_type | table | type | possible_keys      | key                | key_len | ref                  | rows | Extra |
+----+-------------+-------+------+--------------------+--------------------+---------+----------------------+------+-------+
|  1 | SIMPLE      | b     | ALL  | PRIMARY            | NULL               | NULL    | NULL                 |  646 |       |
|  1 | SIMPLE      | a     | ref  | idx_fk_customer_id | idx_fk_customer_id | 2       | sakila.b.customer_id |   11 |       |
+----+-------------+-------+------+--------------------+--------------------+---------+----------------------+------+-------+

(5)type=eq_ref ,类似ref ,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配;简单来说,就是多表连接中使用primary key或者unique index 作为关联条件。(这里mysql在5.6.10版本以后才支持全文索引,如果Mysql版本为早期版本可以考虑将InnoDB,改为MyISAM。

mysql> explain select * from film a,film_text b where a.film_id =b.film_id;

(6)type=const/system, 单表中最多由一个匹配行,查询起来非常迅速,所以这个匹配行中的其他列的值可以被优化器在当前查询中当作常量来处理 。例如,根据主键primary key 或者唯一索引unique index 进行的查询。

  构造一个查询:

mysql> alter table customer add unique index uk_email(email);
Query OK, 0 rows affected (0.25 sec)
Records: 0  Duplicates: 0  Warnings: 0
mysql> explain select * from (select * from customer where email='[email protected]')a;
+----+-------------+------------+--------+---------------+----------+---------+------+------+-------+
| id | select_type | table      | type   | possible_keys | key      | key_len | ref  | rows | Extra |
+----+-------------+------------+--------+---------------+----------+---------+------+------+-------+
|  1 | PRIMARY     |  | system | NULL          | NULL     | NULL    | NULL |    1 |       |
|  2 | DERIVED     | customer   | const  | uk_email      | uk_email | 153     |      |    1 |       |
+----+-------------+------------+--------+---------------+----------+---------+------+------+-------+
2 rows in set (0.05 sec)

通过唯一索引uk_email访问的时候,类型type为const;而从我们构造的仅有一条记录的a表中检索时,类型type就为system。

(7) type =NULL ,MySQL不用访问表或者索引,直接就能够得到结果,例如:

mysql> explain select 1 from dual where 1;
+----+-------------+-------+------+---------------+------+---------+------+------+----------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra          |
+----+-------------+-------+------+---------------+------+---------+------+------+----------------+
|  1 | SIMPLE      | NULL  | NULL | NULL          | NULL | NULL    | NULL | NULL | No tables used |
+----+-------------+-------+------+---------------+------+---------+------+------+----------------+
1 row in set (0.00 sec)

        类型type 还有其他的值,如ref_or_null(与ref类似,区别在于条件中包含对NULL的查询)、

         index_merge(索引合并优化)、unique_subquery (in的后面时一个查询主键字段的子查询)、

  index_subquery(与unique_subquery类似,区别在于in的后面时查询非唯一索引字段的子查询)等。

第二部分:MySQL4.1开始引入了explain extended 命令,通过explain extended 加上show warnings, 我们能够看到在SQL真正被执行之前优化器做了哪些SQL改写:

mysql> explain extended select sum(amount) from customer a,payment b where 1=1 and a.customer_id=b.customer_id
    -> and email='[email protected]';
+----+-------------+-------+-------+--------------------+--------------------+---------+-------+------+----------+-------------+
| id | select_type | table | type  | possible_keys      | key                | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-------+-------+--------------------+--------------------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | a     | const | PRIMARY,uk_email   | uk_email           | 153     | const |    1 |   100.00 | Using index |
|  1 | SIMPLE      | b     | ref   | idx_fk_customer_id | idx_fk_customer_id | 2       | const |   28 |   100.00 |             |
+----+-------------+-------+-------+--------------------+--------------------+---------+-------+------+----------+-------------+
2 rows in set, 1 warning (0.07 sec)
mysql> show warnings;
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                                                      |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | select sum(`sakila`.`b`.`amount`) AS `sum(amount)` from `sakila`.`customer` `a` join `sakila`.`payment` `b` where ((`sakila`.`b`.`customer_id` = '77') and (('[email protected]' = '[email protected]'))) |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

   explain extended 输出结果中多了filtered字段,同时从warning 的message 字段能够看到优化器自动去除了1=1恒成立的条件, 也就是说优化器在改写SQL时会自动去掉恒定成立的条件。

  在遇到复杂的SQL时,我们可以利用explain extended 的结果来迅速的获取一个更清晰易读的SQL。

MySQL 5.1开始支持分区功能,同时explain 命令也增加了对区分的支持。

可以通过explain partitions 命令查看SQL所访问的分区。

例如:创建一个Hash分区的customer_part 表,根据分区键查询的时候,能够看到explain partitions 的输出结果中有一列partitions,其中显示SQL所需要访问的分区名字p2:

 create table customer_part(
    -> customer_id smallint(5) unsigned NOT NULL AUTO_INCREMENT,
    ->  store_id TINYINT UNSIGNED NOT NULL,  first_name VARCHAR(45) NOT NULL,  last_name VARCHAR(45) NOT NULL,  email VARCHAR(50) DEFAULT NULL,  address_id SMALLINT UNSIGNED NOT NULL,  active BOOLEAN NOT NULL DEFAULT TRUE,  create_date DATETIME NOT NULL,  last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,  PRIMARY KEY  (customer_id),  KEY idx_fk_store_id (store_id),  KEY idx_fk_address_id (address_id),  KEY idx_last_name (last_name)
    -> )partition by hash(customer_id) partitions 8;
Query OK, 0 rows affected (0.41 sec)

mysql> insert into customer_part select * from customer;
Query OK, 599 rows affected (0.10 sec)
Records: 599  Duplicates: 0  Warnings: 0

mysql> explain partitions select * from customer_part where customer_id=130;
+----+-------------+---------------+------------+-------+---------------+---------+---------+-------+------+-------+
| id | select_type | table         | partitions | type  | possible_keys | key     | key_len | ref   | rows | Extra |
+----+-------------+---------------+------------+-------+---------------+---------+---------+-------+------+-------+
|  1 | SIMPLE      | customer_part | p2         | const | PRIMARY       | PRIMARY | 2       | const |    1 |       |
+----+-------------+---------------+------------+-------+---------------+---------+---------+-------+------+-------+
1 row in set (0.00 sec)

有的时候,仅仅通过explain 分析执行计划并不能很快定位SQL的问题,这个时候我们还可以选择profile联合分析。

你可能感兴趣的:(SQL)