共计 4769 个字符,预计需要花费 12 分钟才能阅读完成。
explain的作用
查看sql的执行计划,帮助我们分析mysql是如何解析sql语句的
- 查看表的加载顺序
- 查看sql的查询类型
- 哪些索引可能被使用,哪些索引实际被使用
- 表之间的引用关系
- 一个表中有多少行被优化器查询
- 其他的额外信息
执行计划字段
列名 | 说明 |
id | 执行编号,标识select所属的列。如果语句中没有子查询或关联查询,只有唯一的select,每行都将显示1。否则,内层的select语句会按顺序编号,对应其在原始语句中的位置 |
select_type | 显示本行是简单或复杂select |
table | 访问引用的是哪个表(如果是<drivered2>表示衍生表,2指id=2的执行计划) |
type | 读取操作的类型(all,index,range,ref,eq_ref,const/system,NULL) |
possible_keys | 可能用到的索引 |
key | 实际用到的索引 |
key_len | 实际使用到的索引长度 |
ref | 当使用索引列等值查询时,与索引列进行等值匹配的对象信息 |
rows | 为了找到所需的行需要读取的行数,估算值,不精确 |
filtered | 某个表经过搜索条件过滤后剩余记录条数的百分比 |
extra | 额外信息,如using index、filesort等 |
id-执行编号
含义:select查询的序列号,表示查询中执行select子句的顺序
- id相同,执行的顺序是从上到下
- id不同,id值越大,越先被执行
- 同时存在id相同和不同的。id相同的为一组,从上到下顺序执行。在所有组里,id值越大,越先执行
select_type-查询类型
类型 | 说明 |
SIMPLE | 简单的查询,不包含子查询或union |
PRIMARY | 对于包含union或者子查询的大查询来说,它是由几个小查询组成的,其中最左边的那个查询的select_type值就是PRIMARY |
SUBQUERY | 当子查询不能被优化成semi-join,但可以用 materialization来优化,显示为SUBQUERY,表示这是一个不相关子查询,子查询只需要执行一遍,因为结果被存在临时表了 |
DEPENDENT SUBQUERY | 当子查询不能被优化成semi-join,并且是一个相关子查询(或者非相关子查询关闭materialization优化策略时),会被优化器转化为exists相关子查询来进行查询,显示为DEPENDENT SUBQUERY,表示这是一个相关子查询,会根据外查询结果执行多次 |
UNION | 位于 union 中第二个及其以后的查询被标记为UNION,第一个为 PROMARY |
UNION RESULT | Mysql使用临时表来完成UNION查询去重的工作,针对该临时表的查询,select_type就是UNION RESULT |
MATERIALIZED | 当子查询被优化成semi-join执行,并且用的是 Materialize 策略,这个子查询对应显示就是MATERIALIZED,然后子查询结果物化后的临时表与另外一张表进行关联查询 |
DERIVED | 对于无法使用 derived_merge 优化的派生子查询,优化器会采用物化的方式执行的包含派生表的子查询,改派生表对应的子查询select_type就是 DERIVED |
所谓的semi-join是指semi-join子查询。 当一张表在另一张表找到匹配的记录之后,半连接(semi-jion)返回第一张表中的记录。
半连接通常使用IN 或 EXISTS 作为连接条件。 该子查询具有如下结构:
SELECT ... FROM outer_tables WHERE expr IN (SELECT ... FROM inner_tables ...) AND ...
即在where条件的“IN”中的那个子查询。
这种查询的特点是我们只关心outer_table中与semi-join相匹配的记录。即我们只需要从semi-join中获取到最少量的足以对outer_tables记录进行筛选的信息就足够了。
type-读取操作的访问类型
这个一个非常重要的指标,结果值从好到坏依次是:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL,一般来说,要保证查询至少达到 range 级别,最好能达到 ref。
类型 | 说明 |
system | 当表中只有一条记录并且该表使用的存储引擎的统计数据是精确的,比如MyISAM,Memory |
const | 根据主键或者唯一二级索引列与常数进行等值匹配时,对单表的访问方法就是const |
eq_ref | 在连接查询时,如果被驱动表示通过主键或唯一二级索引列等值匹配的方式进行访问的(如果该主键或唯一二级索引是联合索引的话,所有的索引都必须进行等值比较),则对该驱动表的访问方法就是eq_ref |
ref | 当通过普通的二级索引列与常量进行等值匹配时,那么对表的访问方式就是ref |
fulltext | 使用到全文索引 |
ref_or_null | 当对普通二级索引进行等值匹配时,该索引列的值可以是NULL值时,那么对该表的访问方法就是ref_or_null |
index_merge | 一般情况下,对于某个表的查询只能使用一个索引,在某些场景下可以使用 intersection、Union、Sort-Union这三种索引合并的方式进行查询,此时显示的就是index_merge |
unique_subquery | 对于一些包含 in 子查询的查询语句中,如果优化器无法使用 semi-join或物化进行优化,最终将子查询转化为 exists 子查询,而且子查询可以使用到主键或者唯一键进行等值匹配时,此时就是unique_subquery |
index_subquery | 与unique_subquery类似,只不过子查询使用的是普通索引,而不是主键或者唯一索引 |
range | 如果使用索引获取某些范围区间的记录,那么就是range |
index | 需要扫描全部的索引记录,就是index: 1. 索引覆盖,但是where子句不包含索引的第一个字段; 2. 使用索引避免额外的排序 |
ALL | 全表扫描 |
possible_keys 、key
possible_keys 列表示在某个查询语句中,对某个表执行单表查询时可能用到的索引有哪些,key 列表示实际用到的索引有哪些。
有些时候使用到的索引未必在 passible_keys 中,上面 type 为 index 的示例中,passible_keys 显示为 NULL,但实际 key 显示是使用到索引的。possible_keys 列中的值并不是越多越好,可能使用的索引越多,查询优化器计算查询成本时就得花费更长时间,所以如果可以的话,尽量删除那些用不到的索引。
key_len
key_len 列显示 MySQL 决定使用的键长度。如果键是 NULL,则长度为 NULL。使用的索引的长度。在不损失精确性的情况下,长度越短越好 。
ref
当使用索引列等值匹配的条件去执行查询时,也就是在访问方法是 const、eq_ref、ref、ref_or_null、unique_subquery、index_subquery 其中之一时,ref 列展示的就是与索引列作等值匹配的对象是啥。如果不是等值查询,则显示为 NULL。
rows
如果查询优化器决定使用全表扫描的方式对某个表执行查询时,执行计划的 rows 列就代表预计需要扫描的行数;如果使用索引来执行查询时,执行计划的 rows 列就代表预计扫描的索引记录行数。
filtered
对于单表查询来说:
- 如果是全表扫描,filtered 值代表满足 where 条件的行数占表总行数的百分比;
- 如果是使用索引来执行查询,filtered 值代表从索引上取得数据后,满足其他过滤条件的数据行数的占比。
对于关联查询来说,驱动表的 rows*(filtered/100) 代表优化器认为的扇出,对于关联查询的成本估算有很大的影响。举例:
mysql> EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key1 WHERE s1.common_field = 'a';
+----+-------------+-------+------------+------+---------------+----------+---------+-------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+-------------------+------+----------+-------------+
| 1 | SIMPLE | s1 | NULL | ALL | idx_key1 | NULL | NULL | NULL | 9688 | 10.00 | Using where |
| 1 | SIMPLE | s2 | NULL | ref | idx_key1 | idx_key1 | 303 | xiaohaizi.s1.key1 | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+----------+---------+-------------------+------+----------+-------------+
从执行计划中可以看出来,查询优化器打算把 s1 当作驱动表,s2 当作被驱动表。我们可以看到驱动表 s1 表的执行计划的 rows 列为 9688, filtered 列为 10.00,这意味着驱动表 s1 的扇出值就是 9688 × 10.00% = 968.8,这说明还要对被驱动表执行大约 968 次查询。
extra
类型 | 说明 |
using filesort | 使用了排序操作而非索引来生成有序的结果。filesort不一定是利用磁盘文件进行排序,还有可能是在内存里完成的,要看排序的数据大小是否超了设置的缓冲区的大小 |
using temporary | 用临时表保存中间结果,常用语 Group By 操作中,一般看到它说明查询需要优化了 |
using index | 使用索引覆盖的情况 |
using index condition | 发生索引条件下推的情况 |
using where | 全表扫描时,Mysql server层应用 Where 条件过滤数据; 使用索引访问数据,但是where子句中有除了索引包含的字段外的条件 |
using join buffer | 关联查询使用 Block Nested Loop 算法或者 Batched Key Access 算法,会把对驱动表的查询结果放到 join buffer中 |
distinct | 优化 distinct 操作,在找到第一行匹配的元祖后即停止找同样的动作 |
LooseScan | 表示使用了 Semi-join LooseScan 策略优化子查询 |
FirstMatch | 表示使用了 Semi-join FirstMatch 策略优化子查询 |
Start temporary、End temporary | 表示使用了 Semi-join DupilcateWeedout 策略优化子查询 |
ICP(index condition pushdown):索引下推,其实就是实现了index filter技术,将原来的在server层进行的table filter中可以进行index filter的部分,在引擎层面使用index filter进行处理,不再需要回表进行table filter。