本文共 12816 字,大约阅读时间需要 42 分钟。
本文主要介绍下explain。
我们可以通过explian可以查看sql的执行计划,分析sql语句和表结构的性能瓶颈。explain的使用十分简单,通过在查询语句前面加一个explain关键字即可。
更详细资料请查看
explain查看执行计划,共有以下信息返回
查询sql根据复杂程度分以下几个类别
SELECT识别符,每个select语句都会自动分配的一个唯一标识符。SQL执行的顺序的标识。遵循以下原则
查询演员id为1的电影信息
explain select * from film f where f.film_id in (select film_Id from film_actor fa where fa.actor_id=1)- 或者explain select f.* from film f, film_actor fa where fa.film_id = f.film_id and fa.actor_id = 1
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | fa | ref | PRIMARY,idx_fk_film_id | PRIMARY | 2 | const | 19 | 100.0 | Using index | |
1 | SIMPLE | f | eq_ref | PRIMARY | PRIMARY | 2 | sakila.fa.film_id | 1 | 100.0 |
id相同,由上至下,先查fa(film_actor), 再查f(film)表
查询演员id为1和2的电影信息
explain select * from film f where f.film_id in (select film_Id from film_actor fa where fa.actor_id in (1,2));
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | <subquery2> | ALL | 100.0 | |||||||
1 | SIMPLE | f | eq_ref | PRIMARY | PRIMARY | 2 | <subquery2> .film_Id | 1 | 100.0 | ||
2 | MATERIALIZED | fa | range | PRIMARY,idx_fk_film_id | PRIMARY | 2 | 44 | 100.0 | Using where; Using index |
函数
explain select f.title ,(select ceil(rand()*10) from dual) as randnum from film f;
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | PRIMARY | f | index | idx_title | 514 | 1000 | 100.0 | Using index | |||
2 | UNCACHEABLE SUBQUERY | No tables used |
explain select film_id, title, release_year from film where rental_rate = 4.99 union select film_id, title, release_year from film where rental_rate = 3.99
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | PRIMARY | film | ALL | 1000 | 10.0 | Using where | |||||
2 | UNION | film | ALL | 1000 | 10.0 | Using where | |||||
UNION RESULT | <union1,2> | ALL | Using temporary |
<union1,2>
表示依赖id为1和2的
select_type
细节比较多,所以单独一节来介绍,具体请查看
table: 访问的表,可能出现的值
表名
: 这不用了多讲解,从一个表中查询<unionM,N>
: 有union操作的时候,UNION RESULT
合并的时候,M和N代码依赖的执行计划id序号<subqueryN>
: 当简单子查询的时候,会出现。表示临时表,N表示执行计划中idderived
: from子查询时候,会出现空
: 一些函数,不需要通过表获取数据示例1
explain select * from film where film_id in (select film_Id from film_actor where actor_id in (1,2));
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | <subquery2> | ALL | 100.0 | |||||||
1 | SIMPLE | film | eq_ref | PRIMARY | PRIMARY | 2 | <subquery2> .film_Id | 1 | 100.0 | ||
2 | MATERIALIZED | film_actor | range | PRIMARY,idx_fk_film_id | PRIMARY | 2 | 44 | 100.0 | Using where; Using index |
示例2
explain select film_id, title, release_year from film where rental_rate = 4.99 union select film_id, title, release_year from film where rental_rate = 3.99
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | PRIMARY | film | ALL | 1000 | 10.0 | Using where | |||||
2 | UNION | film | ALL | 1000 | 10.0 | Using where | |||||
UNION RESULT | <union1,2> | ALL | Using temporary |
示例3
explain select tn.randnum from (select rand()*10 as randnum from dual) as tn;
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | PRIMARY | <derived2> | system | 1 | 100.0 | ||||||
2 | DERIVED | No tables used |
<derived2>
表示依赖于id为1的,而且是派生表
paritions对于分区表才会有效,未分区表则未空。
分区按照维度可以分为水平和垂直
举个简单例子:一个包含了大text和BLOB列的表,这些text和BLOB列又不经常被访问,这时候就要把这些不经常使用的text和BLOB了划分到另一个分区,在保证它们数据相关性的同时还能提高访问速度。
如果对分区表有兴趣的,可以阅读下这篇文章,这就不多介绍了。
分区建表语句
CREATE TABLE p_student ( id int NOT NULL AUTO_INCREMENT COMMENT 'id', name varchar(30) default '' COMMENT '姓名', grade int(2) default 1 COMMENT '年级', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ciPARTITION BY range (id) (PARTITION p0 VALUES LESS THAN (10000),PARTITION p1 VALUES LESS THAN (20000),PARTITION p2 VALUES LESS THAN (30000) ,PARTITION p3 VALUES LESS THAN (40000) ,PARTITION p5 VALUES LESS THAN (50000),PARTITION p6 VALUES LESS THAN (60000),PARTITION p7 VALUES LESS THAN MAXVALUE );
sql查询
explain select * from p_student where id < 30000
执行计划结果
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | p_student | p0,p1,p2 | range | PRIMARY | PRIMARY | 4 | 1 | 100.0 | Using where |
id小于30000的数据在
p0,p1,p2
分区中
type显示的是访问类型,是较为重要的一个指标,结果值从好到坏依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > unique_subquery > index_subquery > range > index_merge > index > ALL
一般来说,得保证查询至少达到range级别,最好能达到ref
主键
或唯一索引
扫描,对于每个索引键,表中只有一条记录
与之匹配,且为非空not null。between 、< 、> 、in
等查询,这种给定范围扫描比全表扫描要好。因为他只需要开始于索引的某一点,而结束于另一点,不用扫描全部索引。type
细节比较多,所以单独一节来介绍,具体请查看
possible_keys: 可能使用到的索引,实际不一定使用
key: 实际使用的索引,为null表示没有使用索引 key_len: 索引使用的字节数这三个我们一起来演示
使用索引的示例
explainselect language_id from film where language_id = 1
有where条件,language_id是普通索引,不是全表扫描。会有多条数据返回,所以是ref,而不是const。
执行计划结果
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | film | ref | idx_fk_language_id | idx_fk_language_id | 1 | const | 1000 | 100.0 | Using index |
没有使用索引的示例
explainselect * from film where language_id = 1
有where条件,,language_id是普通索引,不是全表扫描,查询字段是所有字段,language_id记录很多。
mysql认为全表all扫描处理的效率比通过language_id
找到对于的主键索引后,再通过主键索引找到字段的效率高。所以使用了all
执行计划结果
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | film | ALL | idx_fk_language_id | 1000 | 100.0 | Using where |
即使possible_keys有值,但是
key
还时为空,即实际也不一定使用到。
一般key都为一个值,当type为index_merge时,会出现多个值。
查询sql
explainselect * from film where film_id =10 or title ='ACADEMY DINOSAUR' or original_language_id =2
由于结果比较长,所以使用列方式打印出来了。
执行计划结果
Name | Value |
---|---|
id | 1 |
select_type | SIMPLE |
table | film |
partitions | |
type | index_merge |
possible_keys | PRIMARY,idx_title,idx_fk_original_language_id |
key | PRIMARY,idx_title,idx_fk_original_language_id |
key_len | 2,514,2 |
ref | |
rows | 3 |
filtered | 100.0 |
Extra | Using union(PRIMARY,idx_title,idx_fk_original_language_id); Using where |
计算公式
列类型 | KEY_LEN | 备注 |
---|---|---|
int | key_len = 4+1 | int为4bytes,允许为NULL,加1byte |
bigint not null | key_len=8 | bigint为8bytes |
char(30) utf8 | key_len=30*3+1 | utf8每个字符为3bytes,允许为NULL,加1byte |
varchar(30) not null utf8 | key_len=30*3+2 | utf8每个字符为3bytes,变长数据类型,加2bytes |
varchar(30) utf8 | key_len=30*3+2+1 | utf8每个字符为3bytes,允许为NULL,加1byte,变长数据类型,加2bytes |
text(10) utf8 | key_len=30*3+2+1 | TEXT截取部分,被视为动态列类型。 |
key_len只指示了where中用于条件过滤时被选中的索引列,是不包含order by/group by这一部分被选中的索引列的。
可以为null的长度需要
+1
。如果不记得类型长度的可以查看下第一节的
我们以film来说明,我们先看下其索引的结构
CREATE TABLE film ( film_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, title VARCHAR(128) NOT NULL, description TEXT DEFAULT NULL, release_year YEAR DEFAULT NULL, language_id TINYINT UNSIGNED NOT NULL, original_language_id TINYINT UNSIGNED DEFAULT NULL, rental_duration TINYINT UNSIGNED NOT NULL DEFAULT 3, rental_rate DECIMAL(4,2) NOT NULL DEFAULT 4.99, length SMALLINT UNSIGNED DEFAULT NULL, replacement_cost DECIMAL(5,2) NOT NULL DEFAULT 19.99, rating ENUM('G','PG','PG-13','R','NC-17') DEFAULT 'G', special_features SET('Trailers','Commentaries','Deleted Scenes','Behind the Scenes') DEFAULT NULL, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (film_id), KEY idx_title (title), KEY idx_fk_language_id (language_id), KEY idx_fk_original_language_id (original_language_id), CONSTRAINT fk_film_language FOREIGN KEY (language_id) REFERENCES language (language_id) ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT fk_film_language_original FOREIGN KEY (original_language_id) REFERENCES language (language_id) ON DELETE RESTRICT ON UPDATE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
film里每个索引计算长度结果为
索引名 | 字段 | 类型 | 长度(字节) | 备注 |
---|---|---|---|---|
PRIMARY KEY | film_id | SMALLINT | 2 bytes | SMALLINT为 2 bytes,不允许为NULL,不加1byte |
idx_title | title | VARCHAR(128) | 128*4+2=514 bytes | utf8mb4每个字符为4bytes,变长数据类型,加2bytes,不为NULL,不加1byte |
idx_fk_language_id | language_id | TINYINT | 1 byte | TINYINT为 1 bytes,不允许为NULL,不加1byte |
idx_fk_original_language_id | original_language_id | TINYINT | 1 byte | TINYINT为 1 bytes,不允许为NULL,不加1byte |
我们使用的编码时
utf8mb4
,而不是utf8
。所以一个字符是4个字节。
下面我们以不同的索引作为条件来查询,查看执行计划结果。
以lfilm_id查询
explainselect film_id from film where film_id = 1
key_len
的长度为2
以language_id查询
explainselect film_id from film where language_id = 1
key_len
的长度为2
以title查询
explainselect title from film where title = 'ACADEMY DINOSAUR'
key_len
的长度为514
128*4+2=514
utf8mb4每个字符为4bytes,变长数据类型,加2bytes,不为NULL,不加1byte
ref 会显示以下几种值
::: tip
条件列上要有索引,如果不是索引的话,那么是all全表,ref为空 :::explainselect * from film where title = 'AFRICAN EGG'
执行计划结果
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | film | ref | idx_title | idx_title | 514 | const | 1 | 100.0 |
title = 'AFRICAN EGG'
其中AFRICAN EGG
为常量,且title为索引列,所以ref为const
explainselect film_actor.actor_id , film.title from film_actor left join film on film.film_id = film_actor.film_id
执行计划结果
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | film_actor | index | idx_fk_film_id | 2 | 5462 | 100.0 | Using index | |||
1 | SIMPLE | film | eq_ref | PRIMARY | PRIMARY | 2 | sakila.film_actor.film_id | 1 | 100.0 |
关联查询,被驱动表
film
的ref
列,显示驱动表film_actor
的关联字段sakila.film_actor.film_id
rows:这里是执行计划中估算的扫描行数,不是精确值
filtered: server过滤后数据给客户端数量/引擎层返回给server数据量百分比。 这个字段表示存储引擎返回的数据在server层过滤后,剩下多少满足查询的记录数量的比例,注意是百分比,不是具体记录数。explainselect film_actor.actor_id , film.title from film_actor left join film on film.film_id = film_actor.film_idwhere film_actor.actor_id = 1
执行计划结果
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | film_actor | ref | PRIMARY | PRIMARY | 2 | const | 19 | 100.0 | Using index | |
1 | SIMPLE | film | eq_ref | PRIMARY | PRIMARY | 2 | sakila.film_actor.film_id | 1 | 100.0 |
通常来说
rows
越小越好,io消耗越少。
filtered
是百分比,越大越好,说明引擎层给server层的数据都是有用的数据。
extra: 一些重要的额外信息。这个列可以显示的信息非常多,有几十种,常用的有
extra细节比较多,所以单独一节来介绍,具体请查看
转载地址:http://tkhws.baihongyu.com/