标题 | 链接 |
---|---|
SQL优化 实战 | >>点击查看<<正在编写ing |
在日常开发过程中,由于时间紧迫,项目组成员水平不一,会导致很多的SQL执行很慢. 此时我们一般会使用Explain
命令来查看这些SQL语句的执行计划,来分析SQL的执行过程.查看该SQL语句有没有使用上索引,有没有做全表扫描 等等. 我们深入的了解MySQl基于开销的优化器,还可以获得很多可能被优化器考虑到的访问策略的细节,以及当运行SQL语句时哪种策略预计会被优化器采用。
使用Explain
关键字可以模拟优化器执行SQL语句,分析查询语句或是结构的性能瓶颈。在select语句之前增加explaion关键字,MySQL会在查询上设置一个标记,执行查询会返回执行计划的信息,而不是执行SQL。
Explain
用途Explain
语法explain SQL语句
例:
explain select * from user
mysql> explain select * from userInfo where id = 1 \G
******************************************************
id: 1
select_type: SIMPLE
table: userInfo
partitions: NULL
type: const
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: const
rows: 1
filtered: 100.00
Extra: NULL
******************************************************
HeidiSQL Portable 9.4
explain select * from user
字段 | 解释 |
---|---|
id | select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序 |
select_type | 查询类型 |
tab | 正在访问哪个表 |
partitions | 匹配的分区 |
type | 访问的类型 |
possible_keys | 显示可能应用在这张表中的索引,一个或多个,但不一定实际使用到 |
key | 实际使用到的索引,如果为NULL,则没有使用索引 |
key_len | 表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度 |
ref | 显示索引的哪一列被使用了,如果可能的话,是一个常数,哪些列或常量被用于查找索引列上的值 |
rows | 根据表统计信息及索引选用情况,大致估算出找到所需的记录所需读取的行数 |
filtered | 查询的表行占表的百分比 |
Extra | 包含不适合在其它列中显示但十分重要的额外信息 |
说明:
从上至下 顺序执行
脚本:
explain
select t1.* , t2.* , t3.*
from actor t1 , film t2 , film_actor t3
where t3.film_id = t2.id and t3.actor_id = t1.id
执行结果:
解说:
如图所示,ID列的值全为1,代表执行顺序从t1表开始加载,依次为t3,t2
说明:
如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
脚本:
explain
select t1.*
from actor t1
where t1.id = (select t2.actor_id from film_actor t2 where t2.id=3)
执行结果:
解说:
如上图所示 , t2表的id值为2 , 所以执行顺序将先从t2表开始加载,然后再执行t1表
说明:
脚本:
explain
select t1.* from actor t1 left join film_actor t2 on t1.id = t2.actor_id
union
select t1.* from actor t1 right join film_actor t2 on t1.id = t2.actor_id
select_type
字段Select_type:查询的类型,
要是用于区别:普通查询、联合查询、子查询等的复杂查询
类型 | 描述 |
---|---|
SIMPLE | 简单的select查询中不好喊子查询或者UNION |
PRIMARY | 查询中若包含任何复杂的子部分,最外层查询则被标记为 |
SUBQUERY | 在SELECT或WHERE列表中包含了子查询 |
DERIVED(衍生) | 在FROM列表中包含的子查询被标记为DERIVED(衍生). MySQL会递归执行这些子查询, 把结果放在临时表里。 |
UNION | 若第二个SELECT出现在UNION之后,则被标记为UNION |
UNION RESULT | 从UNION表获取结果的SELECT |
SIMPLE
说明:
简单的select查询中不好喊子查询或者UNION
脚本:
explain
select t1.* from actor t1 ,film_actor t2 , film t3
where t1.id = t2.actor_id and t3.id = t2.film_id
PRIMARY
与 SUBQUERY
说明:
PRIMARY
: 查询中若包含任何复杂的子部分,最外层查询则被标记为主查询
SUBQUERY
: 在SELECT或WHERE列表中包含了子查询
脚本:
explain
select t1.*
from actor t1
where t1.id = (select t2.actor_id from film_actor t2 where t2.id=3)
DERIVED
说明:
在FROM列表中包含的子查询被标记为DERIVED(衍生)
MySQL会递归执行这些子查询, 把结果放在临时表里。
MySQL5.7+ 进行优化了,增加了derived_merge(派生合并),默认开启,可加快查询效率
UNION RESULT
与UNION
说明:
UNION
:若第二个SELECT出现在UNION
之后,则被标记为UNION
;
UNION RESULT
:从UNION
表获取结果的SELECT
脚本:
explain
select t1.* from actor t1 left join film_actor t2 on t1.id = t2.actor_id
union
select t1.* from actor t1 right join film_actor t2 on t1.id = t2.actor_id
table
字段 显示这一行的数据是关于哪张表的
脚本:
explain
select * from actor
type
列 type显示的是访问类型,是较为重要的一个指标,结果值从最好到最坏依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
需要记忆的
NULL>system>const>eq_ref>ref>range>index>ALL
一般来说,得保证查询至少达到range级别,最好能达到ref。
System
与 const
说明:
System
:表只有一行记录(等于系统表),这是const类型的特列,平时不会出现,这个也可以忽略不计
const
:表示通过索引一次就找到了. const
用于比较primary key
或者unique
索引。因为只匹配一行数据,所以很快. 如将主键置于where列表中,MySQL就能将该查询转换为一个常量
脚本:
explain
select * from (select * from actor t1 where t1.id = 1) s1 ;
eq_ref
说明:
唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描
脚本:
explain
select * from actor t1 left join film_actor t2 on t2.id = t1.id ;
ref
说明:
非唯一性索引扫描,返回匹配某个单独值的所有行. 本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体
表示的是联合索引.
脚本:
explain
select count(distinct actor_id) from film_actor t1 where t1.actor_id = 1
Range
说明:
脚本:
explain
select t1.* from actor t1 where t1.id between 1 and 3
possible_keys
与 key
列possible_keys
:可能使用的key
Key
:实际使用的索引。如果为NULL,则没有使用索引
查询中若使用了覆盖索引
,则该索引和查询的select字段重叠
这里的覆盖索引非常重要,后面会单独的来讲
解说:
其中key和possible_keys都可以出现null的情况(结婚邀请朋友的例子)
- 结婚时,邀请了自己认为回来的朋友—> possible_keys
- 实际到场的朋友---->key
key_len
列 说明:
Key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好
key_len
显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len
是根据表定义计算而得,不是通过表内检索出的
注意:
更具底层使用的不同存储引擎,受影响的行数这个指标可能是一个估计值,也可能是一个精确值. 即使受影响的行数是一个估计值(例如 当使用InnoDB存储引擎管理表存储时),通常情况下这个估计只是一个足以使优化器租出一个有充分依据 的决定
- key_len表示索引使用的字节数,
- 根据这个值,就可以判断索引使用情况,特别是在组合索引的时候,判断所有的索引字段是否都被查询用到。
- char和varchar跟字符编码也有密切的联系,
- latin1占用1个字节,gbk占用2个字节,utf8占用3个字节。(不同字符编码占用的存储空间不同)
以上这个表列出了所有字符类型,但真正建所有的类型常用情况只是CHAR、VARCHAR
脚本:
CREATE TABLE `s1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` char(10) NOT NULL,
`addr` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
explain select * from s1 where name='enjoy';
name这一列为char(10),字符集为utf-8占用3个字节
Keylen=10*3
脚本:
CREATE TABLE `s2` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` char(10) DEFAULT NULL,
`addr` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
explain select * from s2 where name='enjoyedu';
name这一列为char(10),字符集为utf-8占用3个字节,外加需要存入一个null值
Keylen=10*3+1(null) 结果为31
结果允许为null时,需要+1
脚本:
CREATE TABLE `s3` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(10) NOT NULL,
`addr` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
explain select * from s3 where name='enjoyeud';
解说:
Keylen=varchar(n)变长字段+不允许Null=n*(utf8=3,gbk=2,latin1=1)+2
脚本:
CREATE TABLE `s4` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(10) NOT NULL,
`addr` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
explain select * from s4 where name='enjoyeud';
解说:
Keylen=varchar(n)变长字段+允许Null=n*(utf8=3,gbk=2,latin1=1)+1(NULL)+2
CREATE TABLE `numberKeyLen ` (
`c0` int(255) NOT NULL ,
`c1` tinyint(255) NULL DEFAULT NULL ,
`c2` smallint(255) NULL DEFAULT NULL ,
`c3` mediumint(255) NULL DEFAULT NULL ,
`c4` int(255) NULL DEFAULT NULL ,
`c5` bigint(255) NULL DEFAULT NULL ,
`c6` float(255,0) NULL DEFAULT NULL ,
`c7` double(255,0) NULL DEFAULT NULL ,
PRIMARY KEY (`c0`),
INDEX `index_tinyint` (`c1`) USING BTREE ,
INDEX `index_smallint` (`c2`) USING BTREE ,
INDEX `index_mediumint` (`c3`) USING BTREE ,
INDEX `index_int` (`c4`) USING BTREE ,
INDEX `index_bigint` (`c5`) USING BTREE ,
INDEX `index_float` (`c6`) USING BTREE ,
INDEX `index_double` (`c7`) USING BTREE
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
ROW_FORMAT=COMPACT
;
EXPLAIN
select * from numberKeyLen where c1=1
EXPLAIN
select * from numberKeyLen where c2=1
EXPLAIN
select * from numberKeyLen where c3=1
EXPLAIN
select * from numberKeyLen where c4=1
EXPLAIN
select * from numberKeyLen where c5=1
EXPLAIN
select * from numberKeyLen where c6=1
EXPLAIN
select * from numberKeyLen where c7=1
datetime类型在5.6中字段长度是5个字节
datetime类型在5.5中字段长度是8个字节
CREATE TABLE `datatimekeylen ` (
`c1` date NULL DEFAULT NULL ,
`c2` time NULL DEFAULT NULL ,
`c3` year NULL DEFAULT NULL ,
`c4` datetime NULL DEFAULT NULL ,
`c5` timestamp NULL DEFAULT NULL ,
INDEX `index_date` (`c1`) USING BTREE ,
INDEX `index_time` (`c2`) USING BTREE ,
INDEX `index_year` (`c3`) USING BTREE ,
INDEX `index_datetime` (`c4`) USING BTREE ,
INDEX `index_timestamp` (`c5`) USING BTREE
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
ROW_FORMAT=COMPACT
;
EXPLAIN
SELECT * from datatimekeylen where c1 = 1
EXPLAIN
SELECT * from datatimekeylen where c2 = 1
EXPLAIN
SELECT * from datatimekeylen where c3 = 1
EXPLAIN
SELECT * from datatimekeylen where c4 = 1
EXPLAIN
SELECT * from datatimekeylen where c5 = 1
NOT NULL=字段本身的字段长度
NULL=字段本身的字段长度+1(因为需要有是否为空的标记,这个标记需要占用1个字节)
datetime类型在5.6中字段长度是5个字节,datetime类型在5.5中字段长度是8个字节
Ref
列 说明:
显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值
脚本:
explain
select * from actor t1 , film_actor t2 where t1.id = t2.actor_id and t2.film_id=2
执行结果:
解说:
由key_len
可知t1表的actor_id
被充分使用,
actor_id
匹配t2表的id
,film_id
匹配了一个常量,即 1
其中 【mysql_explain.t2.actor_id】 为 【数据库.表.列】
Rows
列 说明:
根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数
脚本:
explain
select * from actor t1 , film_actor t2 where t1.id = t2.actor_id and t2.film_id=2
Extra
列包含不适合在其它列中显示但十分重要的额外信息
值 | 描述 |
---|---|
Using filesort | 说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成的排序操作称为"文件排序" |
Using temporary | 使了用临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序 order by 和分组查询 group by。 |
Using index | 表示相应的select操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错! 如果同时出现using where,表明索引被用来执行索引键值的查找; 如果没有同时出现using where,表明索引用来读取数据而非执行查找动作 |
Using where | 使用了where条件 |
Using join buffer | 使用了连接缓存 |
impossible where | where子句的值总是false,不能用来获取任何元素 |
distinct | 一单mysql找到了与形相联合匹配的行,就不在搜索了 |
说明:
- 由于Markdown 编辑格式问题, 描述不是很全
- 在下面各个值说明中,比较完整
Using filesort
说明:
说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。
MySQL中无法利用索引完成的排序操作称为“文件排序”
当发现有Using filesort 后,实际上就是发现了可以优化的地方
脚本:
explain
select * from film_actor t1 where t1.film_id=1 order by t1.actor_id
执行结果:
解说:
上图其实是一种索引失效的情况,后面会讲,可以看出查询中用到了个联合索引,索引分别为actor_id,film_id
Using temporary
说明:
使用了临时表保存中间结果,MySQL在对结果排序时使用临时表,常见于排序order by 和分组查询group by
脚本:
explain
select * from film_actor t1 where t1.film_id=1 group by t1.remark
执行结果:
解说:
尤其发现在执行计划里面有using filesort而且还有Using temporary的时候,特别需要注意
Using index
说明:
表示相应的select操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错!
如果同时出现using where,表明索引被用来执行索引键值的查找
脚本:
explain
select * from film t1 where t1.name="我的快乐购"
执行结果:
如果没有同时出现using where,表明索引用来读取数据而非执行查找动作
脚本:
explain
select * from film t1
注意:
如果要使用覆盖索引,一定要注意select列表中只取出需要的列,不可select *,
因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降。所以,千万不能为了查询而在所有列上都建立索引,会严重影响修改维护的性能。
Using where
与 using join buffer
说明:
Using where
:表明使用where过滤
using join buffer
:使用了连接缓存
很容易理解,不做案例演示
脚本:
-- 查询MySQl 默认的join_buffer_size
show VARIABLES like '%join_buffer_size%'
impossible where
说明:
where子句的值总是false,不能用来获取任何元组
脚本:
-- where 条件的字句总是为false
explain
select * from film t1 where 1=2
--
explain
select * from film t1 where t1.name="随机填入" and t1.name="随机填入"
### 5.9.6 `distinct`
说明:
一旦mysql找到了与行相联合匹配的行,就不再搜索了
脚本:
explain
select distinct t1.id from actor t1 ,film_actor t2 , film t3 where t1.id = t2.actor_id and t2.film_id = t3.id
Select tables optimized away
说明:
SELECT操作已经优化到不能再优化了(MySQL根本没有遍历表或索引就返回数据了)
脚本:
explain
select min(t1.id) from actor t1
执行结果:
-- actor建表语句:
CREATE TABLE `actor` (
`id` int(11) NOT NULL,
`name` varchar(45) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
-- film建表语句:
CREATE TABLE `film` (
`id` int(11) NOT NULL,
`name` varchar(10) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
-- film_actor建表语句:
CREATE TABLE `film_actor` (
`id` int(11) NOT NULL,
`film_id` int(11) NOT NULL,
`actor_id` int(11) NOT NULL,
`remark` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_film_actor_id` (`film_id`,`actor_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
-- actor 表
INSERT INTO `actor` (`id`, `name`, `update_time`) VALUES (3, '丽丽', '2020-02-28 22:50:57');
INSERT INTO `actor` (`id`, `name`, `update_time`) VALUES (1, '媛媛', '2020-02-28 22:49:53');
INSERT INTO `actor` (`id`, `name`, `update_time`) VALUES (2, '锐锐', '2020-02-28 22:50:44');
-- film 表
INSERT INTO `film` (`id`, `name`) VALUES (3, '授课老师看到');
INSERT INTO `film` (`id`, `name`) VALUES (1, '我的快乐购');
INSERT INTO `film` (`id`, `name`) VALUES (2, '哈哈杜拉拉');
-- film_actor 表
INSERT INTO `film_actor` (`id`, `film_id`, `actor_id`, `remark`) VALUES (5, 3, 2, NULL);
INSERT INTO `film_actor` (`id`, `film_id`, `actor_id`, `remark`) VALUES (6, 3, 2, NULL);
INSERT INTO `film_actor` (`id`, `film_id`, `actor_id`, `remark`) VALUES (2, 2, 1, NULL);
INSERT INTO `film_actor` (`id`, `film_id`, `actor_id`, `remark`) VALUES (4, 2, 3, NULL);
INSERT INTO `film_actor` (`id`, `film_id`, `actor_id`, `remark`) VALUES (1, 1, 1, NULL);
INSERT INTO `film_actor` (`id`, `film_id`, `actor_id`, `remark`) VALUES (3, 1, 3, NULL);