什么是MySQL的执行计划?
执行计划通常是开发者优化SQL语句的第一步。MySQL在解析SQL语句时,会生成多套执行方案,然后内部会进行一个成本的计算,然后通过优化器选择一个最优的方案执行,然后根据这个方案会生成一个执行计划。开发者通过查看SQL语句的执行计划,可以直观的了解到MySQL是如何解析执行这条SQL语句的,然后再针对性的进行优化。执行计划能显示mysql执行sql时的详细执行情况
语法
explain select * from user;
学习执行计划的意义:
了解SQL执行计划的意义就在于我们可以通过执行计划更加清晰的认识到这一条语句,分为了哪几步,有没有用到索引,是否有一些可优化的地方等。
下面开始一项项说。
实际运行一个EXPLAIN时候,我们都能看到下面的这个表头,下面我们一项项的来说明
执行计划中各字段的解释
一 id (select查询的序列号)
包含一组数字,表示查询中执行select子句或操作表的顺序
三种情况:
1、id相同:认为是同一组,执行顺序由上至下
2、id不同:如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
3、id相同又不同(两种情况同时存在):id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行
id为null的情况:
比较少见,id为null的部分一定是最后执行的
假如2有两张表
user 表:
CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(100) DEFAULT NULL, `password` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_name` (`username`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1
address表:
CREATE TABLE `address` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) DEFAULT NULL, `address` varchar(200) CHARACTER SET utf8 DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1
表数据如下:
user表:
id | username | password |
---|---|---|
1 | p1 | 123 |
2 | p2 | 456 |
address表:
id | user_id | address |
---|---|---|
1 | 1 | 邯郸 |
2 | 2 | 德州 |
接下来例子会用到
二 select_type 查询的类型
主要是用于区分普通查询、联合查询、子查询等复杂的查询
序号 | select_type | 描述 | 举例 |
1 | SIMPLE | 不包含任何子查询或union等查询 | EXPLAIN SELECT * FROM user WHERE id=1; |
2 | PRIMARY | 包含子查询最外层查询就显示为 PRIMARY |
EXPLAIN SELECT * FROM user WHERE id=(SELECT user_id FROM address WHERE id=1); |
3 | SUBQUERY | 在select 或 where 字句中包含的查询 |
EXPLAIN SELECT * FROM user WHERE id=(SELECT user_id FROM address WHERE id=1); |
4 | DERIVED | from 字句中包含的查询(衍生查询) |
EXPLAIN SELECT * FROM (SELECT * FROM user WHERE id>1) t; |
5 | UNION | 出现在union 后的查询语句中(当前执行计划的中间记录 就是接下来的 UNION RESULT)) |
EXPLAIN SELECT * FROM user WHERE id=1 UNION SELECT * FROM user WHERE id=2; |
6 | UNION RESULT | 从UNION中获取结果集当前执行计划的最后一条记录 就是 UNION RESULT) |
EXPLAIN SELECT * FROM user WHERE id=1 UNION SELECT * FROM user WHERE id=2; |
三 type 访问类型
sql查询优化中一个很重要的指标,结果值从好到坏依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
一般来说,好的sql查询至少达到range级别,最好能达到ref
1 system 表只有一行记录(等于系统表),这是const类型的特例,平时不会出现,可以忽略不计
表示通过索引或者唯一索引一次就找到了,const用于比较primary key 或者 unique索引。因为只需匹配一行数据,所有很快。如果将主键置于where列表中,mysql就能将该查询转换为一个const 3、eq_ref 唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键 或 唯一索引扫描。
注意:ALL全表扫描的表记录最少的表如t1表
4 ref 使用非唯一索引查找数据
EXPLAIN SELECT id FROM user
WHERE username=‘p1’;
7 ALL 全表扫描以找到匹配的行
四 possible_keys
查询涉及到的字段上存在索引,则该索引将被列出,但不一定被查询实际使用
五 key
实际使用的索引,如果为NULL,则没有使用索引。
查询中如果使用了覆盖索引,则该索引仅出现在key列表中
六 key_len
表示索引中使用的字节数,查询中使用的索引的长度(最大可能长度),并非实际使用长度,理论上长度越短越好。
key_len是根据表定义计算而得的,不是通过表内检索出的,
长度也和字符编码有关系,latin1字符占1个字节 ,一个GBK字符占用2个字节,一个UTF8字符占用3个字节
字符类型
序号 | 字段类型 | 是否允许为null | 字符编码 | key-lenth | 原因 |
1 | char(10) | 不许为null | utf8 | 30 | 10*3 |
2 | char(10) | 允许为null | utf8 | 31 | null需要1个字节的额外空间 |
3 | varchar(10) | 不许为null | utf8 | 32 | 变长字段需要额外的2个字节 |
4 | varchar(10) | 允许为null | utf8 | 33 | (10*3+2+1)变长字段需要额外的2个字节,null需要1个字节额外空间 |
数值类型
tinyint 1字节
smallint 2字节
int 4字节
时间类型 datetime
5.6版本中 6字节
5.5版本及之前 8字节
七 ref
显示索引的哪一列被使用了,如果可能,是一个常量const。
八 rows
根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数
九 Extra
不适合在其他字段中显示,但是十分重要的额外信息
1、Using filesort :文件排序
mysql对数据使用一个外部的索引排序,而不是按照表内的索引进行排序读取。也就是说mysql无法利用索引完成的排序操作称为“文件排序”
出现了Using filesort 需要做优化
由于索引是先按email排序、再按address排序,所以查询时如果直接按address排序,索引就不能满足要求了(此处理解最左优先匹配即可),mysql内部必须再实现一次“文件排序”
2、Using temporary:使用了临时表
使用临时表保存中间结果,也就是说mysql在对查询结果排序时使用了临时表,常见于order by 和 group by
出现了Using temporary 需要做优化
表示相应的select操作中使用了覆盖索引(Covering Index),避免了访问表的数据行,效率高,出现using index 说明SQL还不错
如果同时出现Using where,表明索引被用来执行索引键值的查找(参考上图)
如果没用同时出现Using where,表明索引用来读取数据而非执行查找动作
覆盖索引(Covering Index):也叫索引覆盖。就是select列表中的字段,只用从索引中就能获取,不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖。
a、如需使用覆盖索引,select列表中的字段只取出需要的列,不要使用
select *
b、如果将所有字段都建索引会导致索引文件过大,反而降低crud性能
4、Using where :
使用了where过滤
5、Using join buffer :
使用了链接缓存
6、Impossible WHERE:
where子句的值总是false,不能用来获取任何元祖