在使用MySQL时,如果发现查询语句耗时,会进行排查及调优,其中常用的一个方法是用explain查看sql执行计划。
(1) explain小试牛刀
通过一个简单的例子来了解explain的使用
CREATE TABLE `t2` (
`id` int NOT NULL AUTO_INCREMENT,
`a` int DEFAULT NULL,
`b` int DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_a` (`a`),
KEY `idx_b` (`b`)
) ENGINE=InnoDB ;
往表 t2 中插入 10 万行记录,取值按整数递增,即:(1,1,1),(2,2,2),(3,3,3) 直到 (100000,100000,100000)。
用存储过程来插入10万数据,sql如下:
delimiter ;;
create procedure idata()
begin
declare i int;
set i=1;
while(i<=100000)do
insert into t values(i, i, i);
set i=i+1;
end while;
end;;
delimiter ;
call idata();
假设需要执行下面这条语句,语句的执行结果最多只有1条,因为有limit 1
,但是表t2有2个索引 idx_a idx_b ,MySQL会选用哪个?
如果是我们来分析,肯定是选idx_a,因为 a between 1 and 1000
只需要扫描idx_a索引1000个值,而 b between 50000 and 100000
需要扫描idx_b索引50000个值
mysql> select * from t where (a between 1 and 1000) and (b between 50000 and 100000) order by b limit 1;
mysql> explain select * from t2 where (a between 1 and 1000) and (b between 50000 and 100000) order by b limit 1;
+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+
| 1 | SIMPLE | t2 | NULL | range | a,b | b | 5 | NULL | 50128 | 1.00 | Using index condition; Using where |
+
1 row in set, 1 warning (0.00 sec)
mysql>
结合慢查询日志
# Time: 2022-12-10T17:11:35.593173+08:00
# User@Host: admin[admin] @ localhost [127.0.0.1] Id: 16
# Query_time: 0.105205 Lock_time: 0.000003 Rows_sent: 0 Rows_examined: 50001
SET timestamp=1670663495;
select * from t2 where (a between 1 and 1000) and (b between 50000 and 100000) order by b limit 1;
慢日志的Rows_examined
:执行该SQL,实际扫描的行数,真实值
慢日志的Rows_sent
:执行该SQL,实际返回的行数
之前优化器选择使用索引 b,是因为它认为使用索引 b 可以避免排序(b 本身是索引,已经是有序的了,如果选择索引 b 的话,不需要再做排序,只需要遍历),所以即使扫描行数多,也判定为代价更小。
mysql> explain select * from t2 where (a between 1 and 1000) and (b between 50000 and 100000) order by b,a limit 1;
+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+
| 1 | SIMPLE | t2 | NULL | range | a,b | a | 5 | NULL | 1000 | 50.00 | Using index condition; Using where; Using filesort |
+
1 row in set, 1 warning (0.01 sec)
mysql>
order by b,a 这种写法,要求按照 b,a 排序,就意味着使用这两个索引都需要排序。因此,扫描行数成了影响决策的主要条件,于是此时优化器选了只需要扫描 1000 行的索引 a。
# Time: 2022-12-10T17:27:04.352721+08:00
# User@Host: admin[admin] @ localhost [127.0.0.1] Id: 16
# Query_time: 0.020284 Lock_time: 0.000020 Rows_sent: 0 Rows_examined: 1000
SET timestamp=1670664424;
select * from t2 where (a between 1 and 1000) and (b between 50000 and 100000) order by b,a limit 1;