Explain或desc
SHOW WARNINGS; 这条语句可以查看MySQL执行的原生语句 而不是我们所编写的执行前的语句 MySQL5.6版本在执行计划中加入EXTENDED参数才能查看show warnings
-
ID 执行计划第一列
-
单纯的join id 都是1且从上到下分析
ref 出现在被驱动表中
在没有索引的时候驱动表的数据会保存在join_buffer_size里面,然后在进行join
所以执行join操作的时候,启动表要用小表
-
- subquery,scala subquery都会使id递增
subquery是from后面的子查询
scala subquery select和from之间的标量子查询
-
select_type
- simple 不使用union或者subquery的简单query
- primary 使用union或者subquery的不能被合并的 query
-
union 使用union结合的select除了第一个之外的select select_type用union
union result是union去掉重复值的临时表
union all不会出现union result 因为不去重
-
subquery 这里的subquery是不使用在from后面的subquery
注意点:跟外部表没啥关联 相当于一个常数字段
执行计划没有错误 不代表SQL执行没有错误
-
dependent subquery 必须依附于外面的值 如scala subquery或者exists
- scala subquery
2. exists
3. 两个都在就分不清了
-
derived(派生表)
From后面表的位置上的subquery
Derived是生成在内存或者临时表空间中的
如果derived当作驱动表的时候 要点:要以减少数据量为目的
当作被驱动表的时候产生auto_key索引也要以数据量为目的
5.7 optimizer_switch='derived_merge=on' 可以把简单subquery打开成join
- 5.7 optimizer_switch='derived_merge=on'影响和5.6升级5.7问题
讨论derived_merge on/off对SQL优化有什么影响
1. 为on的时候 被驱动表的连接条件需要索引
2. 为off的时候 被驱动表结果集要小
- 5.6和5.7对于子查询的视图合并会去除order by
例如如下语句
```mysql
SELECT d.*,e.* FROM (SELECT * FROM t_group ORDER BY emp_no DESC) d JOIN employees e ON d.emp_no=e.emp_no
```
5.6版本回执行子查询的order by 但是5.7及以上版本会视图合并 优化掉order by不执行排序 可使用limit等手段阻止视图合并
- Derived不能合并的几种情况
1. union/union all
2. group
3. distinct
4. 聚合函数
5. limit
6. @(自定义变量)
```mysql
WITH w1 AS ( SELECT t.*, 1 n FROM fm_station_detail t ORDER BY id LIMIT 100 ) SELECT
id,
fm_id,
@m := @m + 1 m,
CASE
WHEN @de = fm_id THEN
@row_num := @row_num + 1 ELSE @row_num := 1
END nm,
@dense_rank := @dense_rank +
CASE
WHEN @de = fm_id THEN
0 ELSE n
END dm,
@de := fm_id d1
FROM
w1,
( SELECT @m := 0, @row_num := 0, @dense_rank := 0, @de := "" ) b;
```
-
Materialized
- 使用in的时候产生 也会产生auth_key(semi join的auth_key)索引 相当于
QB_NAME(name) 可以将子查询以块的形式命名
```mysql
SELECT /*+ SEMIJOIN(@sub MATERIALIZATION) */ * FROM t_group t2 WHERE t2.emp_no IN
(SELECT /*+ QB_NAME(sub) */ t.emp_no FROM dept_emp t1);
```
上述SQL表示将子查询命名为sub 并且强制以materialization的形式使用semi join进行查询
-
table
- null 表示不使用任何表或者使用dual
表名或者别名
-
表示临时表 < >里面的数字表示对应的ID列 如下图所示
表示 id=2的临时表 tmp_table_size=max_heap_table_size 适当的调整这个值得大小 可以调整性能
同理
-
type
-
const
使用primary key或者unique获取数据
特点是show warnings可以显示常数
-
-
eq_ref
- 必须是join
- 满足被驱动表的连接条件unique key或者primary key
-
ref
对普通索引列进行 where的等于查询 通常出现在联合索引部分使用的情况中 会产生性能问题
可以通过使用force index() 方式解决
```mysql
SELECT * FROM salaries force index(PRIMARY) WHERE emp_no = 1001 AND from_date >= '1989-06-26';
```
原执行计划为ref 通过添加force index(PRIMARY) 可以将执行计划变为range 提升性能
-
range
索引范围查询 主要出现在 > , < ,between, in,like等范围查询中如下
$\color{red}{range只能在驱动表或者被驱动表查询的驱动表 而不能直接用在被驱动表中 即range只能出现在驱动表中}$
$\color{red}{包含子查询的执行计划出现range表示这个子查询是单独进行的}$
-
index
索引 全扫描 比起表全扫描且order by的请款下快
但是绝大部分情况下是优化对象
Extra 中出现use index说明没有回表 就说明整体性能还可以 整体的i/o小
主要用于
- 不能使用range, const, ref
- 只查询索引列 即不回表
- 使用索引进行排序或者聚合 即省略排序
满足联合索引中 前导列 没有在where条件且查询列在索引或者primary key中 包含emp_no index可用
在聚合运算中group by后面的列在索引或者primary key中且查询列也在索引中index可用
-
all
全表扫描
一般情况下是非常不好的执行计划 但是进行超过大表一半数据量的查询时 效果会更好 并且在整个执行计划中 all越靠后效率越低
导致全表扫描的情况较多 没有索引 对索引列进行加工 索引列进行隐藏类型转换 对日期类型进行like 20% 单列索引的时候 对数字列进行like ‘30%’
-
索引列进行了隐藏类型转换
例如原字段是varchar类型 却使用number类型进行查询 就会导致索引失效造成全表扫描所以查询语句
-
$\color{red}{在执行join操作的时候 如果要使用被驱动表的索引 则驱动表的关联字段类型一定要与被驱动表的关联字段类型一致 字符型就都是字符型 数字型就都是数字型 如果不一样 则要对驱动表的关联字段进行类型转换}$
- 对数字列进行like ‘30%’
$\color{red}{P.S MySQL使用like进行查询时%在前不使用索引 如like ‘%冗余’ 而在后面使用%就会使用索引 如like ‘冗余%’ 这种情况只是针对字符类型才会出现 对于非字符类型的字段 无论%在前还是在后 都不会使用索引}$
- 对日期类型进行like 20%
acess 查询索引
fitered 二次过滤
对上面like ‘1986%’的执行计划分析如下
$\color{red}{select_type是SIMPLE,说明没有子查询。 type为ref,说明使用了联合索引。 possible_keys是可能使用的索引,这里是PRIMARY,emp_no。key是用到的索引,这里只用到了PRIMARY。key_len表示使用的索引长度,这里只用到了4个长度。由下面的执行计划我们可以知道,联合索引被完全使用,key_len应该是7个长度,说明联合索引没有被完全使用。而且出现了filtered,值为11.11,说明发生了二次过滤,只保留了11.11%的结果集,二次过滤发生在回表之后,没有发生ICP。所以进一步说明使用like对非字符类型的字段进行查询,都不会使用索引。}$
-
possible_keys
- 列出表所在的 索引名称
- auto_key 5.6之后的版本开始提供auto_key这个功能
- 所谓auto_key就是临时创建索引 需要消耗一些CPU和内存 对tmp_table_size,max_heap_table_size依赖较大
-
Key
- 实际使用的索引名称 可以使用show index from table查询表的索引情况
上图中cardinality表示的是此索引列去除重复值后的行数
-
key_len
-
可以通过key_len这个值判断在复合索引中具体使用了哪个索引
可以通过下面的例子看出key_len的具体使用
-
-
rows
- 是MySQL优化器根据统计信息预估出来的值 不准确
-
filtered
-
跟rows一样 是预估值 filtered非100的情况是extra有using where关键字 表示从innodb引擎中拿到数据后再加工的比率
这个比率也是我们创建良好索引的依据
MySQL5.7及以上版本通过condition_fanout_filter参数可以配置这个执行计划的显示和隐藏
-
-
Extra
-
Distinct
MySQL在join过程中d中取出一行后 查询de表的时候 碰到一行就停止 有点像exists
用Python表示 和下面代码的执行结果类似 当i=j后 则终止内循环
a = [0,0,1,1,1,2,2,3,3,3,3] for i in range(4) for j in a print(i,j) if i == j break
输出结果如下
0 0 1 0 1 0 1 1 2 0 2 0 2 1 2 1 2 1 2 2 3 0 3 0 3 1 3 1 3 1 3 2 3 2 3 3
触发distinct的条件如下
- select 必须含有distinct关键字
例如下面这个例子 select包含distinct关键字,但是由于驱动表是de,select中只包含d表的字段,所以extra中并没有触发distinct
-
使用straight_join hint强制使用d表成为驱动表优化后 则extra中出现distinct $\color{red}{P.S straight_join可以强制指定驱动表 straight_join左边的表一定是驱动表}$
-
Select tables optimized away
触发条件
- 当select中只有min max count的时候出现
2. 复合主键(两个)的时候 使用其中的任意一个 只能用=号求出另一组且不能包含group by
-
Using filesort
触发条件
- 在进行order by,group by且没使用索引的时候,order by一定会触发,group by在MySQL8.0版本之前会触发
$\color{red}{P.S order by排序和sort_buffer_size参数有关 order by优先在内存中进行 当select的字段过多 数据集超过sort_buffer_size设置的大小时 整个排序就会在磁盘上进行 从而影响查询效率 所以select后面尽量不要跟随*}$
-
Using index
触发条件
-
使用索引且不回表
如果表对应的where条件选择率不好 且一行长度很长 则可以考虑创建包含对应列的索引 减少物理I/O达到优化目的
-
-
Using temporary
MySQL执行过程中为了储存中间结果 会使用temporay table(临时表) 但是不能确定temporay table是生成在内存中还是磁盘中
触发条件
- order by,group by,distinct没有使用索引
2. 执行计划中select_type为derved
3. show session status like ‘%tmp%’
以下两个重要的参数和生成临时表有关 一般是小的起作用 单位是字节
-
Using where
一般using where和filtered,rows一起看 意思是产生了二次过滤
using where表示从存储引擎中拿到一些数据 然后再过滤
其中rows是在存储引擎中拿数据的预估值 filtered是再次过滤的百分比
-
Using index condition(icp 目标是减少回表数量)
触发条件
必须是二级索引才有 且 有一部分索引无法使用的时候
-
必须开启icp。set session optimizer_switch='index_condition_pushdown=on'
下面执行计划表示没有开启ICP之前的执行计划
开启ICP之后
重点如下
$\color{red}{开启之前通过PRIMARY预先查询140w数据,因为只查询了数据表主键,所以需要使用主键回表查询140w全部数据,然后使用from_data字段进行like的二次过滤 二次过滤发生在回表之后}$
$\color{red}{开启之后 使用emp_no联合索引查询主键emp_no和from_data两个字段的140w数据,然后通过from_data字段进行like的二次过滤 过滤之后在回表查询全部字段数据 回表数据是二次过滤后的结果集}$
-
Using MRR
Mrr是二级索引获取pk之后 对pk进行排序来减少随机I/O 达到优化目的
mrr 通过mrr_cost_based控制 set session optimizer_switch='mrr_cost_based=off'之后这可以使用mrr优化
和ICP一样,只有发生回表才会出发
使用PK进行排序占用空间的大小 由read_rnd_buffer_size控制 这个太小了 则不会产生mrr
-
Range checked for each record
有如下SQL
SELECT city, SUM(CASE WHEN op.payment_category IN (1,2,3,4,5,8,9,10) THEN op.payment_amount END) AS amount9, SUM(CASE WHEN op.payment_category IN (6,7,11) THEN op.payment_amount END) AS amount10 FROM op LEFT JOIN o ON o.orders_no = op.orders_no LEFT JOIN ui ON ui.user_id = o.user_id LEFT JOIN qu ON ui.platform = qu.code WHERE op.pqyment_category IN (1,2,3,4,5,6,7,8,9,10,11) AND qu.parent_code IN ('0001', '0002') AND op.create_time >= CONCAT(YEAR(DATE_FORMAT(NOW(), '%Y-%m-%d')), '-1-1 00:00:00') AND op.create_time <= CONCAT(YEAR(DATE_FORMAT(NOW(), '%Y-%m-%d')), '-12-31 23:59:59')
执行计划如下
此时使用show warnings进行查看 发现存在隐式转换
-
Using join buffer (Block Nested Loop)
set session optimizer_switch='block_nested_loop=on,batch_key_access=on'
主要应用于被驱动表没有索引且数据量较少的时候,但大部分情况是优化对象
但是通过改变条件,导致索引失效,效率就会降低。如下图执行计划
几种优化器的相关参数
-
set session optimizer_switch='index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on'
-
materialization=on,semijoin=on,loosescan=on,firstmatch=on,duplicateweedout=on
配置semi join相关参数,8.0.16版本之前都是对in的优化 16版本之后exists和in相同
MariaDB有optimizer_switch里有exists_to_in的参数配置
semijoin=on 控制semi join开关 总开关 loosescan=on 驱动表 索引跳跃式查询 firstmatch=on 被驱动表 索引跳跃式查询 materialization=on 被驱动表auto_key开关 duplicateweedout=on 最基本的semijoin去掉重复值