实践(1)--MySQL机能优化
SQL语句优化-Explain工具
使用EXPLAIN
关键字可以模拟优化器施行SQL语句,剖析你的查询语句或是构造的机能瓶颈 在 select 语句此前增添 explain 关键字,MySQL 会在查询上设定一个标志,施行查询会返回施行方案的信息,而不是施行这条SQL。
留意:假如 from 中包括子查询,仍会施行该子查询,将结果放入暂时表中
Explain剖析示例
DROP TABLE IF EXISTS `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;INSERT INTO `actor` (`id`,`name`,`update_time`) VALUES (1,'a','2020-09-16 14:26:11'), (2,'b','2020-09-16 14:26:11'), (3,'c','2020-09-16 14:26:11');DROP TABLE IF EXISTS` film`; CREATE TABLE`film`(`id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(10) DEFAULT NULL, PRIMARY KEY (`id`),KEY `idx_name` (`name`) )ENGINE=InnoDB DEFAULT CHARSET=utf8;INSERT INTO `film`(`id`,`name`) VALUES (3,'film0'),(1,'film1'),(2,'film2');DROP TABLE IF EXISTS `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;INSERT INTO`film_actor`(`id`,`film_id`,`actor_id`)VALUES(1,1,1), (2,1,2),(3,2,1);复制代码
explain select * from actor;复制代码
![image.png](/uploads/allimg/200930/17f96e7c022f793353be15505ed8b2f0-816970.png)
查询中的每个表会输出一行,假如有两个表通过join
连接查询,那么会输出两行。每一列详细的说明在后面停止说明。
Explain 两个变种
explain extended
会在 explain 的根基上额外供给一些查询优化的信息。紧随其后通过 show warnings 命令可以得到优化后的查询语句,从而看出优化器优化了什么。额外还有 filtered 列,是一个半分比的值,rows * filtered/100 可以预算出将要和 explain 中前一个表停止连接的行数(前一个表指 explain 中的id值比当前表id值小的表)。
explain extended select * from film where id = 1;复制代码
![image.png](/uploads/allimg/200930/17f96e7c022f793353be15505ed8b2f0-916972.png)
show warnings;复制代码
![image.png](/uploads/allimg/200930/10ead8f51cc163468aa02352bed3bc8f-1016975.png)
explain partitions
比拟 explain 多了个 partitions 字段,假如查询是基于分区表的话,会显示查询将拜访的分区。
Explain中的列
接下来我们将展现 explain 中每个列的信息。
id 列
id 列的编号是 select 的序列号,有几个 select 就有几个 id,并且id的次序是按 select 显现的次序递增的。id列越大施行优先级越高,id雷同则从上往下施行,id为 NULL 最后施行。
select_type 列
select_type
表示对应行是简便还是复杂的查询。
simple
:简便查询。查询不包括子查询和union
explain select * from film where id = 2;复制代码
![image.png](/uploads/allimg/200930/10ead8f51cc163468aa02352bed3bc8f-1116977.png)
primary
:复杂查询中最外层的 selectsubquery
:包括在 select 中的子查询(不在 from 子句中)derived
:包括在 from 子句中的子查询。MySQL 会将结果存置在一个暂时表中,也称为派生表(derived的英文含义)
用下面这个例子来理解 primary、subquery、derived类型。
explain select (select 1 from actor where id = 1) from (select * from film where id = 1) der;复制代码
未关闭MySQL5.7新特性对衍生表的合并优化,如下:
![image.png](/uploads/allimg/200930/10ead8f51cc163468aa02352bed3bc8f-1216980.png)
#关闭mysql5.7新特性对衍 生表的合并优化set session optimizer_switch='derived_merge=off'; 复制代码
![image.png](/uploads/allimg/200930/10ead8f51cc163468aa02352bed3bc8f-1316982.png)
#复原默许配置set session optimizer_switch='derived_merge=on'; 复制代码
union
:在 union 中的第二个和随后的 select
explain select 1 union all select 1;复制代码
![image.png](/uploads/allimg/200930/87c2b885227a4f7e79929fbe992f4b8d-1416985.png)
table 列
这一列表示 explain 的一行正在拜访哪个表。 当 from 子句中有子查询时,table列是 格局,表示当前查询依靠 id=N 的查 询,于是先施行 id=N 的查询。 当有 union 时,UNION RESULT 的 table 列的值为<union1,2>,1和2表示参与 union 的 select 行id。
type 列
这一列表示关联类型或拜访类型,即MySQL决议怎样查寻表中的行,查寻数据行记载的大约范畴。
顺次从最优到最差离别为:
system > const > eq_ref > ref > range > index > ALL复制代码
一样来说,得包管查询到达 range
级别,最好到达 ref
。
NULL:MySQL 能够在优化阶段分解查询语句,在施行阶段用不着再拜访表或索引。例如:在索引列中拔取最小值,可以独自查寻索引来完成,不需要在施行时拜访表。
explain select min(id) from film;复制代码
![image.png](/uploads/allimg/200930/87c2b885227a4f7e79929fbe992f4b8d-1516988.png)
system、const :MySQL 能对查询的某部分停止优化并将其转化成一个常量(可以看 show warnings 的)。用于 primary key
或 unique key
的所有列与常数比力时,所以表最多有一个匹配行,读取1次,速度比力快。system 是 const 的特例,表里只要一条元组匹配时为 system。
explain extended select * from (select * from film where id = 1) tmp;复制代码
![image.png](/uploads/allimg/200930/87c2b885227a4f7e79929fbe992f4b8d-1616989.png)
show warnings;复制代码
![image.png](/uploads/allimg/200930/87c2b885227a4f7e79929fbe992f4b8d-1716991.png)
eq_ref :primary key 或 unique key 索引的所有部分被连接使用,最多只会返回一条相符前提的记载。这大概实在 const 之外最好的连接类型了,简便的 select 查询不会显现这种 type。
explain select * from film_actor left join film on film_actor.film_id = film.id;复制代码
![image.png](/uploads/allimg/200930/87c2b885227a4f7e79929fbe992f4b8d-1816994.png)
ref :比拟 eq_ref ,不使用独一索引,而是使用一般索引或者独一性索引的部分前缀,索引要和某个值比拟较,大概会寻到多个相符前提的行。
(1)简便 select 查询,name 是一般索引(非独一索引)
explain select * from film where name = 'film1';复制代码
![image.png](/uploads/allimg/200930/ac0f84943aa1ac191b29249220035af7-1916996.png)
(2)关联表查询, idx_film_actor_id
是 film_id 和 actor_id 的结合索引,这里使用到了 film_actor 的左边前缀 film_id 部分。
explain select film_id from film left join film_actor on film.id = film_actor.film_id;复制代码
![image.png](/uploads/allimg/200930/ac0f84943aa1ac191b29249220035af7-2016997.png)
range : 范畴扫描平常显现在 in()、betwwen、>、<、>=
等操纵中。使用一个索引来检索给定范畴的行。
explain select * from actor where id > 1;复制代码
![image.png](/uploads/allimg/200930/ac0f84943aa1ac191b29249220035af7-2116998.png)
index :扫描全表索引,通过比 ALL 快一些。
explain select * from film;复制代码
![image.png](/uploads/allimg/200930/ac0f84943aa1ac191b29249220035af7-2216999.png)
ALL :即全表扫描,意味着MySQL需要从头到尾去查寻所需要的行。平常状况下这需要增添索引来停止优化了。
explain select * from actor复制代码
![image.png](/uploads/allimg/200930/ac0f84943aa1ac191b29249220035af7-2317000.png)
possible_keys 列
这一列显示查询大概使用哪些索引来查寻。 explain 时大概显现 possible_keys 有列,而 key 显示 NULL 的状况,这种状况是由于表中数据不多,mysql认为索引对此查询帮忙不大,选中了全表查询。 假如该列是NULL,则没有相关的索引。在这种状况下,可以通过检查 where 子句看可否可以制造一个恰当的索引来提高查询机能,然后用 explain 查看结果。
key 列
这一列显示mysql实际采纳哪个索引来优化对该表的拜访。
假如没有使用索引,则该列是 NULL。假如想强迫mysql使用或无视possible_keys列中的索 引,在查询中使用 force index
、ignore index
。
key_len 列
这一列显示了mysql在索引里使用的字节数,通过这个值可以算出详细使用了索引中的哪些 列。 举例来说,film_actor的结合索引 idx_film_actor_id 由 film_id 和 actor_id 两个int列组成, 并且每个int是4字节。通过结果中的key_len=4可推断出查询使用了第一个列:film_id列来执 行索引查寻。
explain select * from film_actor where film_id = 2;复制代码
![image.png](/uploads/allimg/200930/ddb397a1a2af4139257140687bbbf86c-2417003.png)
key_len运算规则如下: 字符串
- char(n):n字节长度
- varchar(n):2字节储备字符串长度,假如是utf-8,则长度 3n +2
数值类型
- tinyint:1字节
- smallint:2字节
- int:4字节
- bigint:8字节
时间类型
- date:3字节
- timestamp:4字节
- datetime:8字节
假如字段同意为 NULL,需要1字节记载可否为 NULL
索引最大长度是768字节,当字符串过长时,mysql会做一个相似左前缀索引的处置,将前半部分的字符提取出来做索引。
ref 列
这一列显示了在key列记载的索引中,表查寻值所用到的列或常量,常见的有:const(常 量),字段名(例:film.id)
rows 列
这一列是mysql估量要读取并检测的行数,留意这个不是结果集里的行数。
Extra 列
这一列展现的是额外信息。常见的重要值如下:
(1)Using index :使用覆盖索引
explain select film_id from film_actor where film_id = 1;复制代码
![image.png](/uploads/allimg/200930/ddb397a1a2af4139257140687bbbf86c-2517008.png)
(2)Using where :使用 where 语句来处置结果,查询的列未被索引覆盖
explain select * from actor where name = 'a';复制代码
![image.png](/uploads/allimg/200930/ddb397a1a2af4139257140687bbbf86c-2617012.png)
(3)Using index condition :查询的列不完全被索引覆盖,where 前提中是一个前导列的范畴
explain select * from film_actor where film_id > 1;复制代码
![image.png](/uploads/allimg/200930/ddb397a1a2af4139257140687bbbf86c-2717015.png)
(4)Using temporary :MySQL 需要创立一张暂时表来处置查询。显现这种状况一样是要停止优化的,第一是想到用索引来优化。
- actor.name没有索引,此时创立了张暂时表来distinct
explain select distinct name from actor;复制代码
![image.png](/uploads/allimg/200930/ddb397a1a2af4139257140687bbbf86c-2817017.png)
- film.name创立了idx_name索引,此时查询时extra是using index,没有用暂时表
explain select distinct name from film;复制代码
![image.png](/uploads/allimg/200930/ddb397a1a2af4139257140687bbbf86c-2917018.png)
(5)Using filesort : 将用外部排序而不是索引排序,数据较小时从内存排序,不然需要在磁盘 完成排序。这种状况下一样也是要思考使用索引来优化的。
- actor.name没有索引,会阅读 actor 整个表,留存排序关键字 name 和对应的 id,然后排序 name 并检索行记载。
explain select * from actor order by name;复制代码
![image.png](/uploads/allimg/200930/8f0336af3581c8bb1e560594c9076b00-3017020.png)
- film.name创立了idx_name索引,此时查询时extra是using index
explain select * from film order by name;复制代码
![image.png](/uploads/allimg/200930/8f0336af3581c8bb1e560594c9076b00-3117024.png)
(6)Select tables optimized away :使用某些聚合函数(比方 max、min)来拜访存在索引 的某个字段是
explain select min(id) from film;复制代码
![image.png](/uploads/allimg/200930/8f0336af3581c8bb1e560594c9076b00-3217027.png)
SQL语句优化-索引最好实践
# 示例表CREATE TABLE`employees`(`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(24) NOT NULL DEFAULT '' COMMENT '姓名',`age` int(11) NOT NULL DEFAULT '0' COMMENT '年龄',`position` varchar(20) NOT NULL DEFAULT '' COMMENT '职位',`hire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间', PRIMARY KEY (`id`), KEY `idx_name_age_position` (`name`,`age`,`position`) USING BTREE )ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='员工记载表'; INSERT INTO employees(name,age,position,hire_time)VALUES('ZhangSan',23,'Manager',NOW());INSERT INTO employees(name,age,position,hire_time)VALUES('HanMeimei', 23,'dev',NOW());INSERT INTO employees(name,age,position,hire_time) VALUES('Lucy',23,'dev',NOW());复制代码
全值匹配
EXPLAIN SELECT * FROM employees WHERE name= 'ZhangSan';复制代码
![image.png](/uploads/allimg/200930/8f0336af3581c8bb1e560594c9076b00-3317030.png)
EXPLAIN SELECT * FROM employees WHERE name= 'ZhangSan' AND age = 22;复制代码
![image.png](/uploads/allimg/200930/8f0336af3581c8bb1e560594c9076b00-3417031.png)
EXPLAIN SELECT * FROM employees WHERE name= 'ZhangSan' AND age = 22 AND position ='manager';复制代码
![image.png](/uploads/allimg/200930/90777812c85165b81c385fe14e642e31-3517032.png)
最左前缀规则
假如索引了多列,要遵照最左前缀规则。指的是查询从索引的最左前列开端并且不跳过索引中的列。
EXPLAIN SELECT * FROM employees WHERE age = 22 AND position ='manager';复制代码
![image.png](/uploads/allimg/200930/90777812c85165b81c385fe14e642e31-3617033.png)
EXPLAIN SELECT * FROM employees WHERE position = 'manager';复制代码
![image.png](/uploads/allimg/200930/90777812c85165b81c385fe14e642e31-3717034.png)
EXPLAIN SELECT * FROM employees WHERE name = 'ZhangSan';复制代码
![image.png](/uploads/allimg/200930/90777812c85165b81c385fe14e642e31-3817035.png)
不在索引列上做任何操纵
不在索引列上做任何操纵(运算、函数、(主动or手动)类型转换),会致使索引失效而转向全表扫描。
EXPLAIN SELECT * FROM employees WHERE name = 'ZhangSan';EXPLAIN SELECT * FROM employees WHERE left(name,3) = 'ZhangSan';复制代码
![image.png](/uploads/allimg/200930/4b3874ac0cd1cb8e2477d436392a5226-3917036.png)
给hire_time增添一个一般索引:
ALTER TABLE `employees`ADD INDEX `idx_hire_time` (`hire_time`) USING BTREE;复制代码
EXPLAIN select * from employees where date(hire_time) ='2020-09-30';复制代码
![image.png](/uploads/allimg/200930/4b3874ac0cd1cb8e2477d436392a5226-4017037.png)
转化为日期范畴查询,会走索引:
EXPLAIN select * from employees where hire_time >='2020-09-30 00:00:00' and hire_time <='2020-09-30 23:59:59';复制代码
![image.png](/uploads/allimg/200930/4b3874ac0cd1cb8e2477d436392a5226-4117038.png)
复原最初索引状态
ALTER TABLE `employees`DROP INDEX `idx_hire_time`;复制代码
储备引擎不克不及使用索引中范畴前提右侧的列
EXPLAIN SELECT * FROM employees WHERE name= 'ZhangSan' AND age = 22 AND position ='manager';EXPLAIN SELECT * FROM employees WHERE name= 'ZhangSan' AND age > 22 AND position ='manager';复制代码
![image.png](/uploads/allimg/200930/4b3874ac0cd1cb8e2477d436392a5226-4217039.png)
尽量使用覆盖索引
尽量使用覆盖索引(只拜访索引的查询(索引列包括查询列)),减少select *
语句。
EXPLAIN SELECT name,age FROM employees WHERE name= 'ZhangSan' AND age = 23 AND position ='manager';复制代码
![image.png](/uploads/allimg/200930/a9ad4049c2ea0625f1454694478d1ea2-4317040.png)
EXPLAIN SELECT * FROM employees WHERE name= 'ZhangSan' AND age = 23 AND position ='manager';复制代码
![image.png](/uploads/allimg/200930/a9ad4049c2ea0625f1454694478d1ea2-4417043.png)
mysql在使用不等于(!=或者<>)的时候没法使用索引会致使全表扫描
EXPLAIN SELECT * FROM employees WHERE name != 'ZhangSan';复制代码
![image.png](/uploads/allimg/200930/a9ad4049c2ea0625f1454694478d1ea2-4517046.png)
is null、is not null
也没法使用索引
EXPLAIN SELECT * FROM employees WHERE name is null复制代码
![image.png](/uploads/allimg/200930/a9ad4049c2ea0625f1454694478d1ea2-4617050.png)
like以通配符开头('%abc...')mysql索引失效会变成全表扫描操纵
EXPLAIN SELECT * FROM employees WHERE name like '%Zhang'复制代码
![image.png](/uploads/allimg/200930/a9ad4049c2ea0625f1454694478d1ea2-4717052.png)
EXPLAIN SELECT * FROM employees WHERE name like 'Zhang%'复制代码
![image.png](/uploads/allimg/200930/a9ad4049c2ea0625f1454694478d1ea2-4817053.png)
问题:解决like'%字符串%'索引不被使用的办法?
- 使用覆盖索引,查询字段必需是创立覆盖索引字段
EXPLAIN SELECT name,age,position FROM employees WHERE name like '%Zhang%';复制代码
![image.png](/uploads/allimg/200930/7850e52c746bccf9509aa2e315e12575-4917055.png)
- 假如不克不及使用覆盖索引则大概需要借助搜索引擎
字符串不加单引号索引失效
EXPLAIN SELECT * FROM employees WHERE name = '1000'; EXPLAIN SELECT * FROM employees WHERE name = 1000;复制代码
![image.png](/uploads/allimg/200930/7850e52c746bccf9509aa2e315e12575-5017056.png)
少用or或in
少用or或in,用它查询时,mysql不必然使用索引,mysql内部优化器会按照检索比例、 表大小等多个因素团体评估可否使用索引,详见范畴查询优化。
EXPLAIN SELECT * FROM employees WHERE name = 'ZhangSan' or name = 'HanMeimei';复制代码
![image.png](/uploads/allimg/200930/7850e52c746bccf9509aa2e315e12575-5117059.png)
范畴查询优化
给年龄增加单值索引
ALTER TABLE`employees`ADD INDEX `idx_age` (`age`)USING BTREE;复制代码
explain select * from employees where age >=1 and age <=2000;复制代码
![image.png](/uploads/allimg/200930/7850e52c746bccf9509aa2e315e12575-5217061.png)
没走索引缘由:mysql内部优化器会按照检索比例、表大小等多个因素团体评估可否使用索引。比方这个例子,大概是由于单次数据量查询过大致使优化器终究选中不走索引 优化办法:可以讲大的范畴拆分成多个小范畴。
explain select * from employees where age >=1 and age <=1000;explain select * from employees where age >=1001 and age <=2000;复制代码
![image.png](/uploads/allimg/200930/7850e52c746bccf9509aa2e315e12575-5317064.png)
复原最初索引状态:
ALTER TABLE `employees`DROP INDEX `idx_age`;复制代码
索引使用总结
假设 index(a,b,c)
![image.png](/uploads/allimg/200930/cfdf938d63810e0b7de81b34dea07bdc-5417065.png)
like KK% 相当于=常量,%KK 和 %KK% 相当于范畴