MySQL-explain-extra字段详解

本文详细介绍了MySQL中EXPLAIN的EXTRA字段的各种情况,包括NULL、Usingindex、Usingwhere、Usingindexcondition以及Usingfilesort和Usingtemporary的含义和应用场景。通过具体的SQL示例,展示了如何根据这些信息进行SQL查询优化,特别是涉及索引覆盖、范围查询和排序操作时的优化策略。
摘要由CSDN通过智能技术生成

转载自:MySQL之explain extra字段解析 - 墨天轮

这里对mysql进行SQL调优的时候肯定是会用到explain来看select语句的执行情况。但是之前我对其中的extra的认识一直是模棱两可的,甚至是错误的。

此篇呢,就主要对extra可能出现的几种情况做个梳理,并测试一把,分别模拟其常见的几种情况。

先上总结:

extrawhere条件select的字段
nullwhere筛选条件是索引的前导列查询的列未被索引覆盖
Using indexwhere筛选条件是索引的前导列查询的列被索引覆盖
Using where; Using indexwhere筛选条件是索引列之一但不是前导列或者where筛选条件是索引列前导列的一个范围查询的列被索引覆盖
Using where;where筛选条件不是索引列-
Using where;where筛选条件不是索引前导列、是索引列前导列的一个范围(>)查询列未被索引覆盖
Using index conditionwhere索引列前导列的一个范围(<、between)查询列未被索引覆盖

两种排序的情况。
这里是参考:http://mysql.taobao.org/monthly/2015/03/04/

extra出现场景
Using filesortfilesort主要用于查询数据结果集的排序操作,首先MySQL会使用sort_buffer_size大小的内存进行排序,如果结果集超过了sort_buffer_size大小,会把这一个排序后的chunk转移到file上,最后使用多路归并排序完成所有数据的排序操作。
Using temporaryMySQL使用临时表保存临时的结构,以用于后续的处理,MySQL首先创建heap引擎的临时表,如果临时的数据过多,超过max_heap_table_size的大小,会自动把临时表转换成MyISAM引擎的表来使用。

两者的不同:
filesort只能应用在单个表上,如果有多个表的数据需要排序,那么MySQL会先使用using temporary保存临时数据,然后再在临时表上使用filesort进行排序,最后输出结果。

什么是索引前导列

所谓前导列,就是在创建复合索引语句的第一列或者连续的多列。比如通过:CREATE INDEX idx_combine ON table_a(x, y, z)创建索引,那么x,xy,xyz都是前导列,而yz,y,z这样的就不是。

什么是索引覆盖

创建一个索引,该索引包含查询中用到的所有字段,称为“覆盖索引”。使用覆盖索引,数据库只需要通过索引就可以查找和返回查询所需要的数据,而不必在使用索引处理数据之后再进行回表操作。

下面呢,我就mock一些数据,来分别验证下这几种情况。

测试数据

创建测试表:

CREATE TABLE `test_order` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `order_id` int(11) DEFAULT NULL,
  `order_status` tinyint(4) DEFAULT NULL,
  `create_date` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_userid_order_id_createdate` (`user_id`,`order_id`,`create_date`)
) ENGINE=InnoDB AUTO_INCREMENT=100002 DEFAULT CHARSET=utf8mb4;


插入数据函数

DELIMITER $$
DROP FUNCTION IF EXISTS `bulk_insert_fun`$$
CREATE  FUNCTION `bulk_insert_fun`() RETURNS INT(11)
BEGIN
 DECLARE num INT DEFAULT 100000;
 DECLARE i INT DEFAULT 0;
 WHILE i<num DO
       insert into test_order (user_id,order_id,order_status,create_date) values (rand()*1000,id,rand()*10,DATE_ADD(NOW(), INTERVAL - RAND()*20000 HOUR));
  SET i = i+1;
 END WHILE;
 RETURN i;
END$$
DELIMITER ;

批量插入数据:

select bulk_insert_fun();

extra是null

表中的索引字段为: idx_userid_order_id_createdate
 (user_id,order_id,create_date)

如下SQL中,指定的where条件为user_id=1, 而user_id是索引前导列。select * 表明查询的列未被索引覆盖(表中还有order_status字段未在索引中)。

mysql> explain select * from test_order where user_id = 1;
+----+-------------+------------+------------+------+--------------------------------+--------------------------------+---------+-------+------+----------+-------+
| id | select_type | table      | partitions | type | possible_keys                  | key                            | key_len | ref   | rows | filtered | Extra |
+----+-------------+------------+------------+------+--------------------------------+--------------------------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | test_order | NULL       | ref  | idx_userid_order_id_createdate | idx_userid_order_id_createdate | 5       | const |  105 |   100.00 | NULL  |
+----+-------------+------------+------------+------+--------------------------------+--------------------------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

可以看到,当where条件是索引的前导列,但是查询的列未被索引覆盖时,此时extra列为null。

这意味着用到了索引,但是部分字段未被索引覆盖,必须通过“回表”来实现,不是纯粹地用到了索引,也不是完全没用到索引,Extra中为NULL。

extra是using index

如下SQL中,指定的where条件为user_id=1, 而user_id是索引前导列。select查询的几个字段user_id,order_id,create_date
都在索引idx_userid_order_id_createdate
中,表明查询的列被索引覆盖。

mysql> explain select `user_id`,`order_id`,`create_date` from test_order where user_id = 1;
+----+-------------+------------+------------+------+--------------------------------+--------------------------------+---------+-------+------+----------+-------------+
| id | select_type | table      | partitions | type | possible_keys                  | key                            | key_len | ref   | rows | filtered | Extra       |
+----+-------------+------------+------------+------+--------------------------------+--------------------------------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | test_order | NULL       | ref  | idx_userid_order_id_createdate | idx_userid_order_id_createdate | 5       | const |  105 |   100.00 | Using index |
+----+-------------+------------+------------+------+--------------------------------+--------------------------------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

可以看到,当where条件既是索引的前导列,查询的列同时也被索引覆盖时,此时extra列为Using index。

extra是Using where; Using index

1.where筛选条件是索引列之一但不是前导列,查询的列被索引覆盖

如下SQL中,指定的where条件为order_id=1, 而order_id不是索引前导列。select查询的几个字段user_id,order_id,create_date
都在索引idx_userid_order_id_createdate
中,表明查询的列被索引覆盖。

mysql> explain select `user_id`,`order_id`,`create_date` from test_order where order_id = 1;
+----+-------------+------------+------------+-------+---------------+--------------------------------+---------+------+--------+----------+--------------------------+
| id | select_type | table      | partitions | type  | possible_keys | key                            | key_len | ref  | rows   | filtered | Extra                    |
+----+-------------+------------+------------+-------+---------------+--------------------------------+---------+------+--------+----------+--------------------------+
|  1 | SIMPLE      | test_order | NULL       | index | NULL          | idx_userid_order_id_createdate | 16      | NULL | 100035 |    10.00 | Using where; Using index |

可见,当查询的列被索引覆盖,但是where筛选条件是索引列之一并不是索引的不是前导列,Extra中为Using where; Using index。此时意味着无法直接通过索引查找来查询到符合条件的数据。

2.where筛选条件是索引列前导列的一个范围,查询的列被索引覆盖

如下SQL中,指定的where条件为user_id>1, user_id是索引前导列,但user_id指定的是一个范围。select查询的几个字段user_id,order_id,create_date
都在索引idx_userid_order_id_createdate
中,表明查询的列被索引覆盖。

mysql> explain select `user_id`,`order_id`,`create_date` from test_order where user_id > 1;
+----+-------------+------------+------------+-------+--------------------------------+--------------------------------+---------+------+-------+----------+--------------------------+
| id | select_type | table      | partitions | type  | possible_keys                  | key                            | key_len | ref  | rows  | filtered | Extra                    |
+----+-------------+------------+------------+-------+--------------------------------+--------------------------------+---------+------+-------+----------+--------------------------+
|  1 | SIMPLE      | test_order | NULL       | range | idx_userid_order_id_createdate | idx_userid_order_id_createdate | 5       | NULL | 50017 |   100.00 | Using where; Using index |
+----+-------------+------------+------------+-------+--------------------------------+--------------------------------+---------+------+-------+----------+--------------------------+
1 row in set, 1 warning (0.01 sec)

我认为,此处'Using where'表示进行查询可能会是表扫描(比如第一种情况),但是如果where条件中有可以用到索引,它就会使用Using index(比如第二种情况)。

extra是Using where

1.where筛选条件不是索引列

如下SQL中,指定的where条件为order_status =0, order_status不是索引前列。此时无论查询的列无论是否被索引覆盖,都是一样的情况。

mysql> explain select * from test_order where order_status =0;
+----+-------------+------------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table      | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+------------+------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | test_order | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 100035 |    10.00 | Using where |
+----+-------------+------------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select `user_id`,`order_id`,`create_date` from test_order where order_status =0;
+----+-------------+------------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table      | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+------------+------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | test_order | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 100035 |    10.00 | Using where |
+----+-------------+------------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)


2.where筛选条件不是索引前导列或者是索引列前导列的一个范围,查询的列未被索引覆盖

mysql> explain select * from test_order where order_id =0;
+----+-------------+------------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table      | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+------------+------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | test_order | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 100035 |    10.00 | Using where |
+----+-------------+------------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select * from test_order where user_id >1;
+----+-------------+------------+------------+------+--------------------------------+------+---------+------+--------+----------+-------------+
| id | select_type | table      | partitions | type | possible_keys                  | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+------------+------------+------+--------------------------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | test_order | NULL       | ALL  | idx_userid_order_id_createdate | NULL | NULL    | NULL | 100035 |    50.00 | Using where |
+----+-------------+------------+------------+------+--------------------------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

using where意味着数据库按表扫描的方式进行where条件的过滤,此时type字段一般是all。这种方式没有用到任何的index,mysql就是从头到尾把整个表遍历一边,找到所需要的数据行。效率是最差的。

extra是Using index condition

如下SQL中,指定的where条件为user_id<5和(user_id>1 and user_id<5), user_id是索引前导列,但user_id指定的是一个范围(注意这里不包含大于,如果大于,那么结果就是上节里的Using where)。select * 表明查询的列未被索引覆盖。

mysql>  explain select * from test_order where user_id<5;
+----+-------------+------------+------------+-------+--------------------------------+--------------------------------+---------+------+------+----------+-----------------------+
| id | select_type | table      | partitions | type  | possible_keys                  | key                            | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+------------+------------+-------+--------------------------------+--------------------------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | test_order | NULL       | range | idx_userid_order_id_createdate | idx_userid_order_id_createdate | 5       | NULL |  454 |   100.00 | Using index condition |
+----+-------------+------------+------------+-------+--------------------------------+--------------------------------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

mysql>  explain select * from test_order where user_id>1 and user_id<5;
+----+-------------+------------+------------+-------+--------------------------------+--------------------------------+---------+------+------+----------+-----------------------+
| id | select_type | table      | partitions | type  | possible_keys                  | key                            | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+------------+------------+-------+--------------------------------+--------------------------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | test_order | NULL       | range | idx_userid_order_id_createdate | idx_userid_order_id_createdate | 5       | NULL |  302 |   100.00 | Using index condition |
+----+-------------+------------+------------+-------+--------------------------------+--------------------------------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)


Where condition表示查询包含索引列和非索引列,优化器将首先解析索引列,并在表中查找其他条件(索引下推)。

此处如果我通过set optimizer_switch='index_condition_pushdown=off';
关闭索引下推,相同的条件extra的信息就会变成Using where。

mysql> set optimizer_switch='index_condition_pushdown=off';
Query OK, 0 rows affected (0.00 sec)

mysql> explain select * from test_order where user_id<5 and user_id>1;
+----+-------------+------------+------------+-------+--------------------------------+--------------------------------+---------+------+------+----------+-------------+
| id | select_type | table      | partitions | type  | possible_keys                  | key                            | key_len | ref  | rows | filtered | Extra       |
+----+-------------+------------+------------+-------+--------------------------------+--------------------------------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | test_order | NULL       | range | idx_userid_order_id_createdate | idx_userid_order_id_createdate | 5       | NULL |  302 |   100.00 | Using where |
+----+-------------+------------+------------+-------+--------------------------------+--------------------------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select * from test_order where user_id<5;
+----+-------------+------------+------------+-------+--------------------------------+--------------------------------+---------+------+------+----------+-------------+
| id | select_type | table      | partitions | type  | possible_keys                  | key                            | key_len | ref  | rows | filtered | Extra       |
+----+-------------+------------+------------+-------+--------------------------------+--------------------------------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | test_order | NULL       | range | idx_userid_order_id_createdate | idx_userid_order_id_createdate | 5       | NULL |  454 |   100.00 | Using where |
+----+-------------+------------+------------+-------+--------------------------------+--------------------------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

Using filesort

如下的SQL,order by的字段是order_status,它是非索引字段,无法使用index的有序性,所以这里是使用了filesort。

mysql>  explain select * from test_order force index(id)  order by test_order.order_status;
+----+-------------+------------+------------+------+---------------+------+---------+------+--------+----------+----------------+
| id | select_type | table      | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra          |
+----+-------------+------------+------------+------+---------------+------+---------+------+--------+----------+----------------+
|  1 | SIMPLE      | test_order | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 100035 |   100.00 | Using filesort |
+----+-------------+------------+------------+------+---------------+------+---------+------+--------+----------+----------------+
1 row in set, 1 warning (0.01 sec)

注:sort_buffer_size这个参数,是个connect级别的参数,MySQL5.7,默认值是1048576字节,也就是1MB,这个例子里需要的排序数据大小肯定是超过这个值了。

extra是Using temporary

如下SQL中,order by的字段在t2表上,所以需要把t1,t2表join的结果保存到temporary表上,然后对临时表进行filesort,最后输出结果。如果把order by的字段替换成t1.id,那么t1的extra信息里会只有Using filesort。

mysql>  explain select * from test_order t1 force index(id), test_order t2 where t1.id = t2.id order by t2.id;
+----+-------------+-------+------------+--------+---------------+---------+---------+-------------------+--------+----------+---------------------------------+
| id | select_type | table | partitions | type   | possible_keys | key     | key_len | ref               | rows   | filtered | Extra                           |
+----+-------------+-------+------------+--------+---------------+---------+---------+-------------------+--------+----------+---------------------------------+
|  1 | SIMPLE      | t1    | NULL       | ALL    | NULL          | NULL    | NULL    | NULL              | 100035 |   100.00 | Using temporary; Using filesort |
|  1 | SIMPLE      | t2    | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | zhenxi_test.t1.id |      1 |   100.00 | NULL                            |
+----+-------------+-------+------------+--------+---------------+---------+---------+-------------------+--------+----------+---------------------------------+
2 rows in set, 1 warning (0.00 sec)

总结

extra字段通常是综合where子句和select的字段来一起判断的。通过上面的示例,我们可以看出其中Using index其实是性能最高的。其他的情况都可以酌情优化。尤其是临时表和文件排序的,更要重点进行优化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值