比如想统计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 | |
+----+-------------+-------+------+--------------------+--------------------+---------+----------------------+------+-------------+
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的后面时查询非唯一索引字段的子查询)等。
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联合分析。