个人随笔
目录
Explain详解与索引最佳实践
2021-06-01 20:28:32

一、Explain工具介绍

使用EXPLAIN关键字可以模拟优化器执行SQL语句,分析你的查询语句或是结构的性能瓶颈 在 select 语句之前增加 explain 关键字,MySQL 会在查询上设置一个标记,执行查询会返 回执行计划的信息,而不是执行这条SQL 注意:如果 from 中包含子查询,仍会执行该子查询,将结果放入临时表中

1、explain 两个变种

1)explain extended:会在 explain 的基础上额外提供一些查询优化的信息。紧随其后通 过 show warnings 命令可以得到优化后的查询语句,从而看出优化器优化了什么。额外还有 filtered 列,是一个半分比的值,rows * filtered/100 可以估算出将要和 explain 中前一个表 进行连接的行数(前一个表指 explain 中的id值比当前表id值小的表)

2)explain partitions:相比 explain 多了个 partitions 字段,如果查询是基于分区表的 话,会显示查询将访问的分区。

2、explain中的列

  1. id列 id列的编号是 select 的序列号,有几个 select 就有几个id,并且id的顺序是按 select 出现的 顺序增长的。 id列越大执行优先级越高,id相同则从上往下执行,id为NULL最后执行

  2. select_type列 select_type 表示对应行是简单还是复杂的查询。
    1)simple:简单查询。查询不包含子查询和union
    2)primary:复杂查询中最外层的 select
    3)subquery:包含在 select 中的子查询(不在 from 子句中)
    4)derived:包含在 from 子句中的子查询。MySQL会将结果存放在一个临时表中,也称为 派生表(derived的英文含义)
    5)union:在 union 中的第二个和随后的 select

3. table列

这一列表示 explain 的一行正在访问哪个表。 当 from 子句中有子查询时,table列是 格式,表示当前查询依赖 id=N 的查 询,于是先执行 id=N 的查询。 当有 union 时,UNION RESULT 的 table 列的值为,1和2表示参与 union 的 select 行id。

4. type列

这一列表示关联类型或访问类型,即MySQL决定如何查找表中的行,查找数据行记录的大概 范围。 依次从最优到最差分别为:system > const > eq_ref > ref > range > index > ALL 一般来说,得保证查询达到range级别,最好达到ref

1)NULL:mysql能够在优化阶段分解查询语句,在执行阶段用不着再访问表或索引。例如:在 索引列中选取最小值,可以单独查找索引来完成,不需要在执行时访问表

2)const, system:mysql能对查询的某部分进行优化并将其转化成一个常量(可以看show warnings 的结果)。用于 primary key 或 unique key 的所有列与常数比较时,所以表最多 有一个匹配行,读取1次,速度比较快。system是const的特例,表里只有一条元组匹配时为 system

3)eq_ref:primary key 或 unique key 索引的所有部分被连接使用 ,最多只会返回一条符合 条件的记录。这可能是在 const 之外最好的联接类型了,简单的 select 查询不会出现这种 type

4)ref:相比 eq_ref,不使用唯一索引,而是使用普通索引或者唯一性索引的部分前缀,索引要 和某个值相比较,可能会找到多个符合条件的行。

5)range:范围扫描通常出现在 in(), between ,> ,<, >= 等操作中。使用一个索引来检索给定 范围的行。

6)index:扫描全表索引,这通常比ALL快一些。

7)ALL:即全表扫描,意味着mysql需要从头到尾去查找所需要的行。通常情况下这需要增加索 引来进行优化了

5. possible_keys列

这一列显示查询可能使用哪些索引来查找。 explain 时可能出现 possible_keys 有列,而 key 显示 NULL 的情况,这种情况是因为表中 数据不多,mysql认为索引对此查询帮助不大,选择了全表查询。 如果该列是NULL,则没有相关的索引。在这种情况下,可以通过检查 where 子句看是否可 以创造一个适当的索引来提高查询性能,然后用 explain 查看效果。

6. key列

这一列显示mysql实际采用哪个索引来优化对该表的访问。 如果没有使用索引,则该列是 NULL。如果想强制mysql使用或忽视possible_keys列中的索 引,在查询中使用 force index、ignore index。

7. key_len列

这一列显示了mysql在索引里使用的字节数,通过这个值可以算出具体使用了索引中的哪些 列。
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 索引最大长度是768字节,当字符串过长时,mysql会做一个类似左前缀索引的处理,将前半 部分的字符提取出来做索引。

8. ref列

这一列显示了在key列记录的索引中,表查找值所用到的列或常量,常见的有:const(常 量),字段名(例:film.id)

9. rows列

这一列是mysql估计要读取并检测的行数,注意这个不是结果集里的行数。

10. Extra列

这一列展示的是额外信息。常见的重要值如下:
1)Using index:使用覆盖索引
2)Using where:使用 where 语句来处理结果,查询的列未被索引覆盖
3)Using index condition:查询的列不完全被索引覆盖,where条件中是一个前导列的范 围;
4)Using temporary:mysql需要创建一张临时表来处理查询。出现这种情况一般是要进行 优化的,首先是想到用索引来优化。
5)Using filesort:将用外部排序而不是索引排序,数据较小时从内存排序,否则需要在磁盘 完成排序。这种情况下一般也是要考虑使用索引来优化的。
6)Select tables optimized away:使用某些聚合函数(比如 max、min)来访问存在索引 的某个字段是

二、索引最佳实践

1.全值匹配

若一个表建了一个联合索引(a,b,c),然后查询条件也是where a=’A’ and b=’B’ and c=’C’;那么这种是最快的,所有条件字段都用了索引。

2.最左前缀法则

如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。其本质的原因是索引底层存放的数据结构,比如建立联合索引(a,b,c),那么底层索引大概存放格式如下
[a]
[b]
[c]

找索引是只能先找a,然后找b然后找c,不能跳过去,比如你的条件是where b=’B’ 那么不会使用索引,如果条件是where a=’A’ and c=’C’,那么也只会用到索引,因为b没有使用到,不能直接跳。

3.不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描

如果我们建了(a)索引,然后查询条件对a有进行操作比如where left(a,3)=’A’,那么索引将失效,导致全表扫描。

4.存储引擎不能使用索引中范围条件右边的列

比如我们建立了索引(a,b,c),那么如果我们的查询条件是where a=’A’ and b>’B’ and c=’C’ ,那么只能使用到索引a,b存储引擎不能使用索引中范围条件右边的列c.

5.尽量使用覆盖索引(只访问索引的查询(索引列包含查询列)),减少select *语句

比如联合索引(a,b,c),那么我们的查询语句最好是select a,b,c from table a=’A’ and b=’B’ and c=’C’.直接查a,b,c那么就不会去查表。

6.mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描

比如建立了索引(a),然后条件是where a!=’A’ ,则无法使用索引,会导致全表扫描。

7.is null,is not null 也无法使用索引

8.like以通配符开头(’%abc…’)mysql索引失效会变成全表扫描操作

比如索引(a),我们的查询条件是where a like ‘%A’,那么不能使用索引,但是where a like ‘A%’可以使用索引,甚至where a like ‘A%B%C’也可以使用索引。

问题:解决like’%字符串%’索引不被使用的方法?
a)使用覆盖索引,查询字段必须是建立覆盖索引字段
b)如果不能使用覆盖索引则可能需要借助搜索引擎

9.字符串不加单引号索引失效

比如索引(a),where a=’A’会走索引,但是where a=A,将导致索引失效。

10.少用or或in,用它查询时,mysql不一定使用索引

mysql内部优化器会根据检索比例、 表大小等多个因素整体评估是否使用索引,详见范围查询优化

11.范围查询优化

索引(a)where a>=1 and a<=2000 可能不会走索引,mysql内部优化器会根据检索比例、表大小等多个因素整体评估是否使用索引。我们可以拆分为小范围where a>=1 and a<=1000 和 where a>=1001 and a<=2000就可以走索引,当然也还得看看mysql内部怎么优化的。

索引使用总结

1.png

三、索引优化

1、Order by与Group by优化

1、MySQL支持两种方式的排序filesort和index,Using index是指MySQL扫描索引本身完成排序。index 效率高,filesort效率低。
2、order by满足两种情况会使用Using index。
1) order by语句使用索引最左前列。
2) 使用where子句与order by子句条件列组合满足索引最左前列。
3、尽量在索引列上完成排序,遵循索引建立(索引创建的顺序)时的最左前缀法则。
4、如果order by的条件不在索引列上,就会产生Using filesort。
5、能用覆盖索引尽量用覆盖索引
6、group by与order by很类似,其实质是先排序后分组,遵照索引创建顺序的最左前缀法则。对于group by的优化如果不需要排序的可以加上order by null禁止排序。注意,where高于having,能写在where中 的限定条件就不要去having限定了。

2、Using filesort文件排序原理详解

filesort文件排序方式
单路排序:是一次性取出满足条件行的所有字段,然后在sort buffer中进行排序;用trace工具可 以看到sort_mode信息里显示< sort_key, additional_fields >或者< sort_key, packed_additional_fields >

双路排序(又叫回表排序模式):是首先根据相应的条件取出相应的排序字段和可以直接定位行数据的行 ID,然后在 sort buffer 中进行排序,排序完后需要再次取回其它需要的字段;用trace工具 可以看到sort_mode信息里显示< sort_key, rowid >

MySQL 通过比较系统变量 max_length_for_sort_data(默认1024字节) 的大小和需要查询的字段总大小来 判断使用哪种排序模式。

如果 max_length_for_sort_data 比查询字段的总长度大,那么使用 单路排序模式;
如果 max_length_for_sort_data 比查询字段的总长度小,那么使用 双路排序模式。

其实对比两个排序模式,单路排序会把所有需要查询的字段都放到 sort buffer 中,而双路排序只会把主键 和需要排序的字段放到sort buffer中进行排序,然后再通过主键回到原表查询需要的字段。

如果MySQL排序内存配置的比较小并且没有条件继续增加了,可以适当把max_length_for_sort_data配置小点,让优化器选择使用双路排序算法,可以在sort_buffer 中一次排序更多的行,只是需要再根据主键回到原表取数据。

如果 MySQL 排序内存有条件可以配置比较大,可以适当增大 max_length_for_sort_data 的值,让优化器 优先选择全字段排序(单路排序),把需要的字段放到 sort_buffer中,这样排序后就会直接从内存里返回查询结果了。所以,MySQL通过max_length_for_sort_data这个参数来控制排序,在不同场景使用不同的排序模式, 从而提升排序效率。

注意,如果全部使用sort_buffer内存排序一般情况下效率会高于磁盘文件排序,但不能因为这个就随便增大sort_buffer(默认1M),mysql很多参数设置都是做过优化的,不要轻易调整。

3、分页查询优化

1、根据自增且连续的主键排序的分页查询
对于这条语句:

  1. select * from employees limit 10000,10;

没添加单独 order by,表示通过主键排序,从表 employees 中取出从 10001 行开始的 10 行记录。看似只查询了 10 条记录,实际这条 SQL 是先读取 10010 条记录,然后抛弃前 10000条记录,然后读到后面 10 条想要的数据。因此要查询一张大表比较靠后的数据,执行效率 是非常低的。

因为主键是自增并且连续的,所以可以改写成按照主键去查询从第 90001开始的五行数据,如下:

  1. select * from employees where id > 90000 limit 5;

这条改写的SQL 在很多场景并不实用,因为表中可能某些记录被删后,主键空缺,导致结果不一致.如果主键不连续,不能使用上面描述的优化方法。

2、根据非主键字段排序的分页查询
关键是让排序时返回的字段尽可能少,所以可以让排序和分页操作先查出主键,然后根据主键查到对应的记录

  1. select * from employees e inner join (select id from employees order by name limit 90000,5) ed on e.id = ed.id;

4、Join关联查询优化

mysql的表关联常见有两种算法:
Nested-Loop Join算法
Block Nested-Loop Join算法

1)嵌套循环连接 Nested-Loop Join(NLJ) 算法
一次一行循环地从第一张表(称为驱动表)中读取行,在这行数据中取到关联字段,根据关联字段在另一张表(被驱动表)里取出满足条件的行,然后取出两张表的结果合集。

优化器一般会优先选择小表做驱动表。所以使用 inner join 时,排在前面的表并不一定就是驱动表。

使用了 NLJ算法。一般 join 语句中,如果执行计划 Extra 中未出现 Using join buffer 则表示使用的 join 算 法是 NLJ.

2)基于块的嵌套循环连接 Block Nested-Loop Join(BNL)算法
把驱动表的数据读入到 join_buffer 中,然后扫描被驱动表,把被驱动表每一行取出来跟 join_buffer 中的数据做对比。

MySQL对于被驱动表的关联字段没索引的关联查询,一般都会使用BNL算法。如果有索引一般选择NLJ算法,有索引的情况下NLJ 算法比BNL算法性能更高

3)对于关联sql的优化
关联sql的优化:关联字段加索引,让mysql做join操作时尽量选择NLJ算法 小标驱动大表:写多表连接sql时如果明确知道哪张表是小表可以用straight_join写法固定连接驱动方式,省去mysql优化器自己判断的时间

straight_join解释:straight_join功能同join类似,但能让左边的表来驱动右边的表,能改表优化器对于联表查询的执 行顺序。
比如:select * from t2 straight_join t1 on t2.a = t1.a; 代表制定mysql选着 t2 表作为驱动表。
straight_join只适用于inner join,并不适用于left join,right join。(因为left join,right join已经代表指 定了表的执行顺序) 尽可能让优化器去判断,因为大部分情况下mysql优化器是比人要聪明的。使用straight_join一定要慎重,因为部分情况下人为指定的执行顺序并不一定会比优化引擎要靠谱。

5、in和exsits优化

原则:小表驱动大表,即小的数据集驱动大的数据集
in:当B表的数据集小于A表的数据集时,in优于exists

  1. select * from A where id in (select id from B)
  2. #等价于:
  3. for(select id from B){
  4. select * from A where A.id = B.id
  5. }

exists:当A表的数据集小于B表的数据集时,exists优于in
将主查询A的数据,放到子查询B中做条件验证,根据验证结果(true或false)来决定主查询的数据是否保留

  1. select * from A where exists (select 1 from B where B.id = A.id)
  2. #等价于:
  3. for(select * from A){
  4. select * from B where B.id = A.id
  5. }
  6. #A表与B表的ID字段应建立索引

1、EXISTS (subquery)只返回TRUE或FALSE,因此子查询中的SELECT * 也可以用SELECT 1替换,官方说法是实际执行时会 忽略SELECT清单,因此没有区别 2、EXISTS子查询的实际执行过程可能经过了优化而不是我们理解上的逐条对比
3、EXISTS子查询往往也可以用JOIN来代替,何种最优需要具体问题具体分析

6、count(*)查询优化

  1. # 临时关闭mysql查询缓存,为了查看sql多次执行的真实时间
  2. mysql> set global query_cache_size=0;
  3. mysql> set global query_cache_type=0;
  4. mysql> EXPLAIN select count(1) from employees;
  5. mysql> EXPLAIN select count(id) from employees;
  6. mysql> EXPLAIN select count(name) from employees;
  7. mysql> EXPLAIN select count(*) from employees;

四个sql的执行计划一样,说明这四个sql执行效率应该差不多,区别在于根据某个字段count不会统计字段为null值的数据行为什么mysql最终选择辅助索引而不是主键聚集索引?因为二级索引相对主键索引存储数据更少,检索性能应该更高.

常见优化方法
1、查询mysql自己维护的总行数
对于myisam存储引擎的表做不带where条件的count查询性能是很高的,因为myisam存储引擎的表的总行数会被 mysql存储在磁盘上,查询不需要计算.

对于innodb存储引擎的表mysql不会存储表的总记录行数,查询count需要实时计算

2、show table status
如果只需要知道表总行数的估计值可以用如下sql查询,性能很高

3、将总数维护到Redis里
插入或删除表数据行的时候同时维护redis里的表总行数key的计数值(用incr或decr命令),但是这种方式可能不准,很难 保证表操作和redis操作的事务一致性

4、增加计数表
插入或删除表数据行的时候同时维护计数表,让他们在同一个事务里操作

 325

啊!这个可能是世界上最丑的留言输入框功能~


当然,也是最丑的留言列表

有疑问发邮件到 : suibibk@qq.com 侵权立删
Copyright : 个人随笔   备案号 : 粤ICP备18099399号-2