Explain——MySQL性能优化

日常工作中经常会遇到sql优化的问题,随着数据量的增大,数据的查询速度越来越慢,这个适合对于数据库和sql的优化必不可少,优化就需要有一点的依据,EXPLAIN 命令可以对 SELECT 语句的执行过程进行分析, 并输出 SELECT 执行的详细信息, 以供开发人员针对性优化。

一、数据准备

生成用户表和订单表,利用存储过程生成一万个用户和十万条订单

-- 用户表
CREATE TABLE `t_user` (
  `id` int(12) NOT NULL AUTO_INCREMENT,
  `name` varchar(64) DEFAULT NULL,
  `age` int(3) DEFAULT NULL,
  `mobile` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8;

DROP PROCEDURE
IF
	EXISTS proc_initData;--如果存在此存储过程则删掉 
DELIMITER $
CREATE PROCEDURE proc_initData () BEGIN
	DECLARE
		i INT DEFAULT 1;
	WHILE
			i <= 10000 DO
			INSERT INTO `test_2`.`t_user` ( `id`, `name`, `age`, `mobile` )
		VALUES
			( i, CONCAT( 'name', i ), FLOOR( 10 + ( RAND() * 60 )), FLOOR( 11000000000 + ( RAND() * 199999999999 )));
		SET i = i + 1;
	END WHILE;
END $ CALL proc_initData ();

-- 订单表
CREATE TABLE `t_order` (
  `id` int(12) NOT NULL AUTO_INCREMENT,
  `member_id` int(12) DEFAULT NULL,
  `amount` int(11) DEFAULT NULL COMMENT '数量',
  `money` int(11) DEFAULT NULL COMMENT '订单金额',
  `status` int(1) DEFAULT NULL COMMENT '订单状态(0:待付款;1:已付款;2:已退款;3:已退货;4:已取消)',
  `order_code` varchar(255) DEFAULT NULL COMMENT '订单编号',
  `order_time` datetime DEFAULT NULL COMMENT '下单时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=100001 DEFAULT CHARSET=utf8;

DROP PROCEDURE
IF
	EXISTS proc_initData;--如果存在此存储过程则删掉 
DELIMITER $
CREATE PROCEDURE proc_initData () BEGIN
	DECLARE
		i INT DEFAULT 1;
	WHILE
			i <= 100000 DO
			INSERT INTO `test_2`.`t_order` ( `id`, member_id, `amount`, `money`, `status`, `order_code`, `order_time` )
		VALUES
			(
				i,
				FLOOR(1 + ( RAND() * 10000 )),
				FLOOR(1 + ( RAND() * 10 )),
				FLOOR(10 + ( RAND() * 1000 )),
				FLOOR(0 + ( RAND() * 5 )),
				CONCAT('TE',FLOOR(11000000000 + ( RAND() * 199999999999 ))),
			DATE_ADD( '2021-01-01 00:00:00', INTERVAL FLOOR( 1 + ( RAND() * 1864000 )) SECOND )
	);
	SET i = i + 1;
END WHILE;
END $ CALL proc_initData ();


-- 会员身份
CREATE TABLE `t_member_type` (
  `id` int(12) NOT NULL AUTO_INCREMENT,
  `member_id` int(12) DEFAULT NULL,
  `member_type_id` int(2) DEFAULT NULL COMMENT '会员身份:1:vip;2:vvip',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `test_2`.`t_member_type`(`id`, `member_id`, `member_type_id`) VALUES (1, 1, 1);

二、EXPLAIN 输出格式

mysql> explain select * from t_user where id < 10;
+----+-------------+--------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table  | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+--------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t_user | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL |    9 |   100.00 | Using where |
+----+-------------+--------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

其中:

  • id: 查询的序列号 。
  • select_type: SELECT 查询的类型。
  • table: 显示这一行的数据是关于哪张表的。
  • partitions: 匹配的分区。
  • type: 类型(重要)。
  • possible_keys: 此次查询中可能选用的索引。
  • key: 此次查询中确切使用到的索引。
  • rows: 表示每张表有多少行被优化器查询。
  • filtered: 表示此查询条件所过滤的数据的百分比。
  • extra: 额外的信息。

三、详细解释

3.1、ID

id相同

当ID相同时,执行顺序由上到下,即先先执行t_order子查询,后执行t_user:

mysql> EXPLAIN SELECT
    -> * 
    -> FROM
    -> t_order 
    -> WHERE
    -> MEMBER_ID IN ( SELECT ID FROM t_user WHERE ID < 10 );
+----+-------------+---------+------------+--------+---------------+---------+---------+--------------------------+-------+----------+-------------+
| id | select_type | table   | partitions | type   | possible_keys | key     | key_len | ref                      | rows  | filtered | Extra       |
+----+-------------+---------+------------+--------+---------------+---------+---------+--------------------------+-------+----------+-------------+
|  1 | SIMPLE      | t_order | NULL       | ALL    | NULL          | NULL    | NULL    | NULL                     | 99867 |   100.00 | Using where |
|  1 | SIMPLE      | t_user  | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | test_2.t_order.member_id |     1 |   100.00 | Using index |
+----+-------------+---------+------------+--------+---------------+---------+---------+--------------------------+-------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)

id不同

  • id 完全不同,如果是子查询 id 的序号会递增: id 越大优先级越高,越先被执行 (正常由内而外执行)
  • id 有相同的有不同的,结合上面的情况,先执行优先级高的,优先级相同的回到上面的情况,先执行上面的;
EXPLAIN SELECT
	* 
FROM
	t_order 
WHERE
	member_id = (
	SELECT
		id 
	FROM
		t_user 
WHERE
	id IN ( SELECT member_id FROM t_member_type WHERE member_type_id = 1 ))

+----+--------------+---------------+------------+--------+---------------+---------+---------+-----------------------+-------+----------+-------------+
| id | select_type  | table         | partitions | type   | possible_keys | key     | key_len | ref                   | rows  | filtered | Extra       |
+----+--------------+---------------+------------+--------+---------------+---------+---------+-----------------------+-------+----------+-------------+
|  1 | PRIMARY      | t_order       | NULL       | ALL    | NULL          | NULL    | NULL    | NULL                  | 99867 |    10.00 | Using where |
|  2 | SUBQUERY     | <subquery3>   | NULL       | ALL    | NULL          | NULL    | NULL    | NULL                  |  NULL |   100.00 | Using where |
|  2 | SUBQUERY     | t_user        | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | <subquery3>.member_id |     1 |   100.00 | Using index |
|  3 | MATERIALIZED | t_member_type | NULL       | ALL    | NULL          | NULL    | NULL    | NULL                  |     2 |    50.00 | Using where |
+----+--------------+---------------+------------+--------+---------------+---------+---------+-----------------------+-------+----------+-------------+
4 rows in set, 1 warning (0.00 sec)

3.2、select_type

字面意思,查询的类型,类型包括:

  • SIMPLE:简单的查询,表示此查询不包含 UNION 查询或子查询;
  • PRIMARY:表示此查询是最外层的查询;
  • UNION:表示此查询是 UNION 的第二或随后的查询;
  • DEPENDENT UNION:UNION 中的第二个或后面的查询语句, 取决于外面的查询;
  • UNION RESULT:UNION 的结果;
  • SUBQUERY:在select或者where中有子查询;
  • DEPENDENT SUBQUERY: 子查询中的第一个 SELECT, 取决于外面的查询. 即子查询依赖于外层查询的结果;

3.3、table

比较简单,显示这一行的数据是关于哪张表的, (上面的 代表子查询)

3.4、partitions

这个字段和使用的索引关系密切,如果在查询 的时候,发现查询需要添加索引,或者添加的索引没有使用到,重新添加索引进行查询。

3.5、type

访问类型,对表访问方式,表示MySQL在表中找到所需行的方式。

  • system > const > eq_erf > ref > range > index > all(一般保证查询至少达到range级别,最好能达到ref)
  • system:表中仅有一行(=系统表)这是const联结类型的一个特例

const

通过索引一次就找到,const用于比较primary key或者unique索引,因为只匹配一行数据,所以如果将主键置于where列表中,mysql能将该查询转换为一个常量

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

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

eq_erf

唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于唯一索引或者主键扫描。

mysql> explain
    -> select *
    -> from t_user,t_member_type
    -> where  t_member_type.member_id = t_user.id
    -> ;
+----+-------------+---------------+------------+--------+---------------+---------+---------+--------------------------------+------+----------+-------------+
| id | select_type | table         | partitions | type   | possible_keys | key     | key_len | ref                            | rows | filtered | Extra       |
+----+-------------+---------------+------------+--------+---------------+---------+---------+--------------------------------+------+----------+-------------+
|  1 | SIMPLE      | t_member_type | NULL       | ALL    | NULL          | NULL    | NULL    | NULL                           |    1 |   100.00 | Using where |
|  1 | SIMPLE      | t_user        | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | test_2.t_member_type.member_id |    1 |   100.00 | NULL        |
+----+-------------+---------------+------------+--------+---------------+---------+---------+--------------------------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)

const 和eq_ref的区别

const是单表直接按主键或唯一键读取;
eq_ref用于联表查询的情况,按联表的主键或唯一键联合查询。
https://dev.mysql.com/doc/refman/8.0/en/explain-output.html#jointype_eq_ref
https://dev.mysql.com/doc/refman/8.0/en/explain-output.html#jointype_const

ref

非唯一性索引扫描,返回匹配某个单独值的所有行,本质上也是一种索引访问,它返回所有匹配某个单独值的行,可能会找多个符合条件的行,属于查找和扫描的混合体

-- 先给t_order的member_id添加一个索引:
create index t_order_member_id_index
	on t_order (member_id);
mysql> explain select * from t_order where member_id = 10;
+----+-------------+---------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-------+
| id | select_type | table   | partitions | type | possible_keys           | key                     | key_len | ref   | rows | filtered | Extra |
+----+-------------+---------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | t_order | NULL       | ref  | t_order_member_id_index | t_order_member_id_index | 5       | const |    7 |   100.00 | NULL  |
+----+-------------+---------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

range

只检索给定范围的行,使用一个索引来选择行,key列显示使用的索引,通常为between、< 、>、in等关键词,扫描的效率大于全表扫描,扫描索引的一部分,不用扫描全部索引

下面的sql都是使用了基于主键提供查询范围,
mysql> explain select count(id) from t_user where id < 10;
+----+-------------+--------+------------+-------+---------------+---------+---------+------+------+----------+--------------------------+
| id | select_type | table  | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+--------+------------+-------+---------------+---------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | t_user | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL |    9 |   100.00 | Using where; Using index |
+----+-------------+--------+------------+-------+---------------+---------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select count(order_code) from t_order where id < 10;
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table   | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t_order | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL |    9 |   100.00 | Using where |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

这条语句相比较第二条,没有使用索引提供扫描范围,所以"type = all"
mysql> explain select count(order_code) from t_order where amount < 10;
+----+-------------+---------+------------+------+---------------+------+---------+------+-------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra       |
+----+-------------+---------+------------+------+---------------+------+---------+------+-------+----------+-------------+
|  1 | SIMPLE      | t_order | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 99867 |    33.33 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+-------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

index

index 与all区别为index类型只遍历索引树,通常比all快,因为索引文件比数据文件小很多

使用了t_user的主键索引
mysql> explain select count(id) from t_user;
+----+-------------+--------+------------+-------+---------------+---------+---------+------+-------+----------+-------------+
| id | select_type | table  | partitions | type  | possible_keys | key     | key_len | ref  | rows  | filtered | Extra       |
+----+-------------+--------+------------+-------+---------------+---------+---------+------+-------+----------+-------------+
|  1 | SIMPLE      | t_user | NULL       | index | NULL          | PRIMARY | 4       | NULL | 10157 |   100.00 | Using index |
+----+-------------+--------+------------+-------+---------------+---------+---------+------+-------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

all

遍历全表以找到匹配的行

没有使用到索引,所以type=all
mysql> explain select count(order_code) from t_order;
+----+-------------+---------+------------+------+---------------+------+---------+------+-------+----------+-------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+-------+----------+-------+
|  1 | SIMPLE      | t_order | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 99867 |   100.00 | NULL  |
+----+-------------+---------+------------+------+---------------+------+---------+------+-------+----------+-------+
1 row in set, 1 warning (0.00 sec)

3.6、possible_keys

哪个索引在该表中找到行

3.7、key

当前查询时所真正使用到的索引.

possible_keys: 数据库内部分析出可能使用的索引
key: sql实际使用过程中使用的索引
通常通过比较两列的值,判断索引十分实现失效,除此之外,如果没有创建索引,但是实际执行过程中key!=null,即覆盖索引:
SQL只需要通过索引就可以返回查询所需要的数据,而不必通过二级索引查到主键之后再去查询数据。

可以调转前往查看:什么叫做覆盖索引?

这里只是简要的说明一下:

mysql> explain select id,mobile from t_user where mobile = '91368457063';
+----+-------------+--------+------------+-------+----------------------+----------------------+---------+-------+------+----------+-------------+
| id | select_type | table  | partitions | type  | possible_keys        | key                  | key_len | ref   | rows | filtered | Extra       |
+----+-------------+--------+------------+-------+----------------------+----------------------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | t_user | NULL       | const | t_user_mobile_uindex | t_user_mobile_uindex | 99      | const |    1 |   100.00 | Using index |
+----+-------------+--------+------------+-------+----------------------+----------------------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

3.8、rows

每张表有多少行被优化器查询 ,扫描的行数,我认为这是最直观的查看到sql执行效率的指标。

3.9、filtered

3.10、extra

包含不适合在其他列显示,但十分重要的信息

using filesort(文件排序,九死一生)

当mysql不能通过不能使用所以对待排序的数据进行排序,需要进行文件排序,如果待排序的数据量过于巨大,可能会占有大量的IO资源。

mysql> explain select * from t_order where member_id  = 1 order by amount desc;
+----+-------------+---------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+---------------------------------------+
| id | select_type | table   | partitions | type | possible_keys           | key                     | key_len | ref   | rows | filtered | Extra                                 |
+----+-------------+---------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+---------------------------------------+
|  1 | SIMPLE      | t_order | NULL       | ref  | t_order_member_id_index | t_order_member_id_index | 5       | const |    9 |   100.00 | Using index condition; Using filesort |
+----+-------------+---------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+---------------------------------------+
1 row in set, 1 warning (0.00 sec)

using temporary(九死零生)

查询过程中使用到了临时表

mysql> explain select * from t_order group by order_time;
+----+-------------+---------+------------+------+---------------+------+---------+------+-------+----------+---------------------------------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra                           |
+----+-------------+---------+------------+------+---------------+------+---------+------+-------+----------+---------------------------------+
|  1 | SIMPLE      | t_order | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 99867 |   100.00 | Using temporary; Using filesort |
+----+-------------+---------+------------+------+---------------+------+---------+------+-------+----------+---------------------------------+
1 row in set, 1 warning (0.00 sec)

using index (棒极啦)

直接从索引中获取数据

using where

使用了where条件

using join buffer

使用了连接缓存

这里推荐一篇好的文章,便于理解:MySQL 优化二(内部优化器以及 explain 使用)(高级篇)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值