Explain工具介绍
使用EXPLAIN关键字可以模拟优化器执行SQL语句,分析你的查询语句或是结构的性能瓶颈在 select 语句之前增加 explain 关键字,MySQL 会在查询上设置一个标记,执行查询会返回执行计划的信息,而不是执行这条SQL注意:如果 from 中包含子查询,仍会执行该子查询,将结果放入临时表中
Explain分析示例1
示例表:
表1
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','2017‐12‐22 15:27:18'),(2,'b','2017‐12‐22 15:27:18'),(3,'c','2017‐12‐22 15:27:18');
表2
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');
表3
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);
在查询中的每个表会输出一行,如果有两个表通过 join 连接查询,那么会输出两行
explain 两个变种
1)explain extended:会在 explain 的基础上额外提供一些查询优化的信息。紧随其后通过 show warnings 命令可以得到优化后的查询语句,从而看出优化器优化了什么
查询主键:
explain select * from pea_goods where pea_goods_Id=190919181614003;
输出结果
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | pea_goods | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 |
show warnings;
输出结果
Level | Code | Message |
---|---|---|
Note | 1003 | /* select#1 */ select '190919181614003' AS pea_goods_id ,'190919145343001' AS pea_supplier_id ,'190919175230001' AS pea_customer_id ,'舒乐安定' AS goods_name ,'XXX007' AS goods_code ,'10' AS goods_type ,'吨' AS goods_unit ,NULL AS safe_date ,'100' AS width ,'100' AS length ,'100' AS height ,'100' AS weight ,NULL AS volume ,'10000.08' AS price ,'2019-10-30 23:59:59' AS warranty ,'30' AS allowed_day ,'危险品,小心被炸' AS description ,NULL AS line_no ,'Y' AS is_active ,'190815135958005' AS created_by ,'2019-09-19 18:16:14' AS created ,'190815135958005' AS updated_by ,'2019-10-10 16:53:02' AS updated ,'190815135742002' AS ad_client_id from vip_tulin .pea_goods where 1 |
查询非主键
explain select * from pea_goods where pea_customer_id=190919175230001;
输出结果
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | pea_goods | ALL | 50 | 10.00 | Using | where |
show warnings;
输出结果
Level | Code | Message |
---|---|---|
Note | 1003 | /* |
select#1 */ select `vip_tulin`.`pea_goods`.`pea_goods_id` AS `pea_goods_id`,`vip_tulin`.`pea_goods`.`pea_supplier_id` AS `pea_supplier_id`,`vip_tulin`.`pea_goods`.`pea_customer_id` AS `pea_customer_id`,`vip_tulin`.`pea_goods`.`goods_name` AS `goods_name`,`vip_tulin`.`pea_goods`.`goods_code` AS `goods_code`,`vip_tulin`.`pea_goods`.`goods_type` AS `goods_type`,`vip_tulin`.`pea_goods`.`goods_unit` AS `goods_unit`,`vip_tulin`.`pea_goods`.`safe_date` AS `safe_date`,`vip_tulin`.`pea_goods`.`width` AS `width`,`vip_tulin`.`pea_goods`.`length` AS `length`,`vip_tulin`.`pea_goods`.`height` AS `height`,`vip_tulin`.`pea_goods`.`weight` AS `weight`,`vip_tulin`.`pea_goods`.`volume` AS `volume`,`vip_tulin`.`pea_goods`.`price` AS `price`,`vip_tulin`.`pea_goods`.`warranty` AS `warranty`,`vip_tulin`.`pea_goods`.`allowed_day` AS `allowed_day`,`vip_tulin`.`pea_goods`.`description` AS `description`,`vip_tulin`.`pea_goods`.`line_no` AS `line_no`,`vip_tulin`.`pea_goods`.`is_active` AS `is_active`,`vip_tulin`.`pea_goods`.`created_by` AS `created_by`,`vip_tulin`.`pea_goods`.`created` AS `created`,`vip_tulin`.`pea_goods`.`updated_by` AS `updated_by`,`vip_tulin`.`pea_goods`.`updated` AS `updated`,`vip_tulin`.`pea_goods`.`ad_client_id` AS `ad_client_id` from `vip_tulin`.`pea_goods` where (`vip_tulin`.`pea_goods`.`pea_customer_id` = 190919175230001)
id 列
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | pea_goods | ALL | 50 | 10.00 | Using | where |
id 列的编号是select 的序列号,有几个select 就有几个id,并且顺序是按照select 顺序增长的。
id 越大执行优先级越高,id 相同从上往下执行,id 为null 最后执行。
2. select_type 列
simple : 表示简单查询 查询不包括 子查询和union
primary: 复杂查询的最外层的select
subquery: select 子句中的子查询
derived: from 子句的子查询
sql :
set session optimizer_switch='derived_merge=off';
-- 关闭 mysql 5.7 新特性对衍生表的合并优化
EXPLAIN SELECT
( SELECT 1 FROM pea_goods WHERE pea_goods_id = 190919182516001 )
FROM
( SELECT * FROM pea_customer WHERE pea_customer_id = 190919175230001 ) goods
比较 primary、subquery 和 derived 类型 >>输出结果:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | PRIMARY | system | 1 | 100.00 | |||||||
3 | DERIVED | pea_customer | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | ||
2 | SUBQUERY | pea_goods | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 |
union:在 union 中的第二个和随后的 select
输出结果:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | PRIMARY | No tables used | |||||||||
1 | UNION |
3. table 列
这一列表示xeplain 的一行正在访问那个表。
from 有子查询的时候 ,table 列 是
当有union ,UNION RESULT的table列 值 为<union 1,2> 1和2 表示参与union的select 和 id。
4. type 列
这一列表示 关联类型或者访问类型,就是 mysql 决定如何查找 表中的行,查找数据行记录的大概范围。
从最优到最差 system>const>eq_ref>ref>rangge>index>ALL
一般来讲 :得保证查询达到range级别,最好达到ref
NULL : mysql 能够 在优化阶段查询语句,在执行阶段不用再访问或索引 。
const ,system : mysql 能对查询的某部分进行优化并将其转化成一个常量
(show warning) 用于primary key 或 unique key 的所有列与常数比较时,所以表最多有一个匹配行,读取一次,速度比较快。system是const 的特例。表里只有一条元祖匹配时为system.
5. possible_keys 列
这一列 显示可能使用那些索引来查找
explain 时可能出现possible_keys 有列,而key 显示NULL的情况,这种情况是因为,表中数据不多,mysql 认为索引对此查询帮助不大,选择了全表查询
如果该列是NULL,则没有相关的索引。在这种情况下,可以通过检查 where 子句看是否可以创造一个适当的索引来提高查询性能,然后用 explain 查看效果
6 key 列
这一列 显示mysql 实际采用哪一个索引来优化对该表的访问,
如果没有使用索引该列为NULL, 如果想强制或忽略 possible_key列中的索引,在查询中使用force_index,ignore_index.
7.key_len 列
这一列显示了mysql 在索引里使用的字节数,通过这个数可以算出来具体索引中的哪些列。
pea_goods_Id 为 bigint 类型 并且是8个字节。
查询主键:
explain select * from pea_goods where pea_goods_Id=190919181614003;
输出结果
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | pea_goods | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 |
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
8 ref 列
这一列显示了在key列记录的索引中,表查找值所用到的列或常量,常见的有:const(常量),字段名(例:film.id)
9 rows 列
这一列是mysql 估计要读取并检测的行数, 注意这个不是结果集里的行数
10 Extra 列
这一列 展示额外信息,常见的重要的指 如下
-
Using index : 使用覆盖索引
-
Using where : 使用where 语句来处理结果,查询列未被索引覆盖
-
Using index condition :查询的列不完全被索引覆盖 ,where 条件中是一个前导列的范围。
-
Using temporary : mysql 需要创建一张临时表来处理查询,出现这种情况一般要进行优化的,首先是想到用索引来优化。
- goods_name 无索引 此时创建了一张临时表
EXPLAIN SELECT DISTINCT goods_name from pea_goods
无索引 结果
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | PRIMARY | pea_goods | ALL | Using temporary |
EXPLAIN SELECT DISTINCT goods_name from pea_goods
有索引结果
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | PRIMARY | pea_goods | ALL | Using index |
- Using filesort
将用外部排序而不是索引排序,数据较小时从内存排序,否则需要在磁盘完成排序。这种情况下一般也是要考虑使用索引来优化的
- goods_name 未创建索引,pea_goods,保存排序关键字pea_goods和对应的id,然后排序pea_goods并检索行记录
EXPLAIN SELECT * from pea_goods ORDER BY goods_name
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | PRIMARY | pea_goods | ALL | Using filesort |
- goods_name 创建索引
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | PRIMARY | pea_goods | ALL | Using index |
- Select tables optimized away :
使用某些聚合函数(比如 max、min)来访问存在索引的某个字段是
EXPLAIN SELECT min(pea_goods_id) from pea_goods ORDER BY goods_name
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | Select tables optimized away |
索引最佳实践
创建表
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 ( 'LiLei',22,'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());
1.全值匹配
1. EXPLAIN SELECT * FROM employees WHERE NAME = 'LiLei';
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra |
---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | employees | ref | idx_name_age_position | idx_name_age_position | 74 | const | 1 |
EXPLAIN SELECT * FROM employees WHERE
NAME = 'LiLei' AND age = 22;
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra |
---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | employees | ref | idx_name_age_position | idx_name_age_position | 78 | const,const | 1 |
EXPLAIN SELECT * FROM employees WHERE NAME = 'LiLei' AND age = 22
ANDposition = 'manager';
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra |
---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | employees | ref | idx_name_age_position | idx_name_age_position | 140 | const,const,const | 1 |
2.最左前缀法则
如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。
EXPLAIN SELECT * FROM employees WHERE age = 22 AND position ='manager';
结果
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | employees | 3 | 33.33 | Using where |
EXPLAIN SELECT * FROM employees WHERE position = 'manager';
结果
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | employees | 3 | 33.33 | Using where |
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei';
结果
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | employees | ref | idx_name_age_position | idx_name_age_position | 74 | const | 1 | 100.00 |
3.不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
EXPLAIN SELECT * FROM employees WHERE name ='LiLei';EXPLAIN SELECT * FROM employees
WHERE left(name,3) = 'LiLei';
结果
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | employees | ref | idx_name_age_position | idx_name_age_position | 74 | const | 1 | 100.00 |
给hire_time增加一个普通索引:
ALTER TABLE `employees`2ADD INDEX `idx_hire_time` (`hire_time`) USING BTREE ;
EXPLAIN select * from employees where date(hire_time) ='2018-09-30';
结果
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | employees | 3 | 100.00 | Using where |
转化为日期范围查询,会走索引:
EXPLAIN select * from employees where hire_time >='2018-09-30 00:00:00'
andhire_time <='2018-09-30 23:59:59';
还原最初索引状态
ALTER TABLE `employees` DROP INDEX `idx_hire_time`;
4.存储引擎不能使用索引中范围条件右边的列
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22 AND position ='manager';
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age > 22 AND position ='manager';
5. 尽量使用覆盖索引(只访问索引的查询(索引列包含查询列)),减少select *语句
EXPLAIN SELECT name,age FROM employees WHERE name= 'LiLei' AND age = 23 AND position ='manager';
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 23 AND position ='manager';
6. mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描
EXPLAIN SELECT * FROM employees WHERE name != 'LiLei';
7. is null,is not null 也无法使用索引
EXPLAIN SELECT * FROM employees WHERE name is null
8. like以通配符开头('$abc...')mysql索引失效会变成全表扫描操作
EXPLAIN SELECT * FROM employees WHERE name like '%Lei'
EXPLAIN SELECT * FROM employees WHERE name like 'Lei%'
问题:解决like'%字符串%'索引不被使用的方法?
a)使用覆盖索引,查询字段必须是建立覆盖索引字段
EXPLAIN SELECT name,age,position FROM employees WHERE name like '%Lei%';
b)如果不能使用覆盖索引则可能需要借助搜索引擎
9.字符串不加单引号索引失效
EXPLAIN SELECT * FROM employees WHERE name = '1000';
EXPLAIN SELECT * FROM employees WHERE name = 1000;
10.少用or或in
用它查询时,mysql不一定使用索引,mysql内部优化器会根据检索比例、表大小等多个因素整体评估是否使用索引,详见范围查询优化
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' or name = 'HanMeimei';
11.范围查询优化给年龄添加单值索引
ALTER TABLE `employees`2ADD INDEX `idx_age` (`age`) USING BTREE ;
explain select * from employees where age >=1 and age <=2000;
没走索引原因:mysql内部优化器会根据检索比例、表大小等多个因素整体评估是否使用索引。比如这个例子,可能是由于单次数据量查询过大导致优化器最终选择不走索引优化方法:可以讲大的范围拆分成多个小范围
explain select * from employees where age >=1 and age <=1000;
explain select * from employees where age >=1001 and age <=2000;
还原最初索引状态
1ALTER TABLE employees
2DROP INDEX idx_age
;
索引使用总结:
假设 index(a,b,c)
where 语句 | 索引是否被使用 |
---|---|
where a=3 | Y ,使用到a |
where a =3 and b =4 | Y ,使用到 a,b |
where a =3 and b =4 and c=5 | Y ,使用到 a,b,c |
where b=3 / where b= 3 and c=4 /where c=5 | N |
where a=3 and c=5 | 使用到a ,但是c不可以, b中间断了 |
where a=3 and b like 'KK%' and c=4 | Y ,使用到a,b,c |
where a=3 and b like '%KK' and c =4 | Y 只用到a |
where a =3 and b like '%KK%' and c=4 | Y 只用到a |
where a =3 and b like 'K%KK%' and c=4 | Y 用到a,b,c |
like KK%相当于=常量,%KK和%KK% 相当于范围