Explain Tool Introduction
Use EXPLAIN keyword can simulate the optimizer to execute SQL statements, analyze the performance bottleneck in your query or keyword structure increases explain before the select statement, MySQL will set a mark on the query, execute query returns execution plan information, instead executes the SQL Note: if from contains sub-queries, will execute the sub-query results into a temporary table
Explain analysis Example 1
Example Table:
Table 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');
Table 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');
table 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);
Each table will output a line in the query, if there are two tables join query by join, then the output will be two lines
explain two variants
1) explain Extended : extra query optimization provide some information on the basis of explain. Followed by the query can be optimized by show warnings commands to see what the optimizer optimization
Primary key query:
explain select * from pea_goods where pea_goods_Id=190919181614003;
Output
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;
Output
Level | Code | Message |
---|---|---|
Note | 1003 | / * SELECT * #. 1 / SELECT '190,919,181,614,003' the AS pea_goods_id , '190,919,145,343,001' the AS pea_supplier_id , '190,919,175,230,001' the AS pea_customer_id , 'estazolam' the AS goods_name , 'XXX007' the AS goods_code , '10 'the AS goods_type ,' t 'the AS goods_unit , the AS NULL safe_date ,' 100 'the AS width ,' 100 'the AS length ,' 100 'the AS height ,' 100 'the AS weight , the AS NULL volume ,' 10000.08 'the AS price ,' 2019-10-30 23:59:59 'the AS warranty , '30' the AS allowed_day , 'dangerous, carefully blown 'the AS description , the AS NULL line_no ,' the Y 'the AS is_active ,' 190,815,135,958,005 'the AS created_by ,' 2019-09-19 18:16:14 'the AS created ,' 190,815,135,958,005 'the AS updated_by ,' 2019-10-10 16:53:02 'the AS updated , '190,815,135,742,002' the AS ad_client_id from vip_tulin .pea_goods where 1 |
Non-primary key query
explain select * from pea_goods where pea_customer_id=190919175230001;
Output
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;
Output
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 column
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 列
the Simple : represents the simple query query does not include sub-queries and of Union
Primary : the outermost select complex queries
subquery : select clause subquery
derived : from sub-query clause
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
Comparison of primary, subquery and derived types >> output:
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 : select the second and subsequent union of the
Output:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | PRIMARY | No tables used | |||||||||
1 | UNION |
3. table column
This column represents xeplain row is accessing the table.
from when there are sub-queries, table column
when there is union, UNION RESULT the table column values <union 1,2> 1 and 2 show the involvement of the union and select id.
4. type column
This column shows the type of association or access type is mysql decide how to find rows in the table, find the approximate range of rows of data.
From best to worst system> const> eq_ref> ref> rangge> index> ALL In general: have to ensure that the query reaches the level range, preferably up to ref
NULL : MySQL can query in the optimization stage, do not have access or index during the implementation phase.
const, System : MySQL can be optimized for a part of the query and converts it to a constant
(show warning) when compared to all the columns and a constant primary key or a unique key for the table so that at most one matching row, read once , faster. const system is a special case. When only one list matching tuples named system.
5. possible_keys 列
This column displays may use the index to find those
that may occur when there is explain possible_keys columns, and display key to NULL, because this situation is not much data in the table, mysql think of little help index this query, select the entire table Inquire
If the column is NULL, no relevant index. In this case, you can see if you can create an appropriate index to improve query performance, then explain to view the results by checking the where clause
6 key column
This column shows which index mysql actually used to optimize access to the table,
if the index is not used as a NULL, if you want to enforce or ignore possible_key column index, use force_index, ignore_index in the query.
7.key_len 列
This column shows the number of bytes used in the index mysql, can be calculated by the number of columns which specific index.
pea_goods_Id bigint type and is 8 bytes.
Primary key query:
explain select * from pea_goods where pea_goods_Id=190919181614003;
Output
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 following calculation rules:
String
- char (n): n byte length
- varchar (n): 2-byte string length storage, if it is utf-8, then the length 3n + 2
Numeric types
- tinyint: 1 byte
- smallint: 2 bytes
- int: 4 bytes
- bigint: 8 bytes
Time Type
- date: 3 bytes
- timestamp: 4 bytes
- datetime: 8-byte field allows If NULL, the need to record is NULL byte 1
8 ref column
This column shows the index key columns of records in a table lookup column values or constants used in common are: const (constant), field names (example: film.id )
9 rows columns
This column is to be read and mysql estimate the number of rows of detection, note that this is not the number of rows in the result set
10 Extra column
This column shows additional information, refer to the following important common
-
Index the Using : Use a covering index
-
Where the Using : Use where statement to the results, the non-indexed columns covers the query
-
Index for condition Condition the Using : column of the query is not completely covered by the index, where conditions are a range of leading column.
-
The Temporary the Using : MySQL needs to create a temporary table to process the query, this situation is generally to be optimized, first thought of using the index to optimize.
- goods_name no index at this time to create a temporary table
EXPLAIN SELECT DISTINCT goods_name from pea_goods
No indexing results
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% 相当于范围