第四章 Explain分析和查询优化

目录
一、复习查询优化器
二、MySQL常见瓶颈
三、Explain
四、各字段解释
五、单表查询优化(索引失效)

一、复习查询优化器

MySQL有专门负责优化select语句的优化器模块

通过计算系统中收集到的统计信息,为请求的query提供他(mysql)认为最优的执行计划,但不见得是DBA认为最优的,这过程最耗时

过程:
(1)客户端向MySQL请求一条Query发给解析器
(2)命令解析器完成请求分类,区别是select并转发给MySQL Query Optimizer
(3)MySQL Query Optimizer对整条Query进行优化,处理一些常量表达式的预算,直接换算成常量值,并对Query中查询条件简化,根据Hint信息以确定执行计划,进行计算分析,最后得出执行计划

二、MySQL常见瓶颈

CPU饱和:发生在磁盘读取数据 || 数据装入内存
磁盘IO瓶颈:装入的数据量 大于 内存容量
查看系统性能状态:top,free,iostat,vmstat等命令查看系统的性能状态
性能下降SQL(慢执行程序||等待长数据库)

1. 查询语句写的慢
   - 各种连接,子查询导致没用索引
   - 没建索引
2. 索引失效(建了索引,没用上)
   - 单值
   - 复合
3. 关联查询太多join(设计缺陷或者不得已需求)
   - 分布式开发的库表分离,同一张表数据过大会分到不同的库
   - 那么join关联太多也会影响效率
4. 服务器调优及各个参数设置(缓冲,线程数)

三、Explain

定义

  1. 使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的
  2. 目的:分析你的查询语句或者表结构是否存在性能瓶颈

作用

  1. 表table的读取顺序------id,table
  2. 数据读取器操作的操作类型------select_type
  3. 哪些索引可以使用----possible_keys
  4. 哪些索引被实际使用----key(索引名)
  5. 表之间的引用----ref(索引具体的列)
  6. 每张表有多少行被优化器查询---rows(被查询的行数)

EXPLAIN+SQL 效果图


image-20200903114210832.png

四、各字段解释

目录

  1. id:select选择标识符
  2. select_type:表示查询的类型
  3. table:输出结果集的表
  4. type:表示表的连接类型
  5. possible_keys:表示查询时可能使用的索引
  6. key:表示实际使用的索引
  7. 覆盖索引
  8. key_len:索引字段的长度
  9. ref:列与索引的比较
  10. rows:扫描出的行数(估算的行数)
  11. Extra:执行情况的描述和说明

1.id:表的读取顺序

id是select查询的序列号(一组数字),表示查询中执行select子句或操作表的顺序
id相同:执行顺序为 从上至下执行(图中红色箭头)

image-20200903153136476.png

id不同:执行顺序为 id大的先执行

image-20200903155759449.png

id相同又不同: 执行顺序为

  • id不同时,值较大的先执行
  • id相同时,从上至下执行(可以认为是同一组)
    image-20200903160343340.png

    表的加载顺序为t3, t2, 虚表dervied2 ** 其中s1中的dervied2** 的 2,为 id = 2,父亲是t3
    总结:由t3衍生s1,s1和t2在里面关联查询,故 t3 > s1 > t2

2. select_type:表示查询的类型

elect_type 属性 含义
SIMPLE 简单的 select 查询,查询中不包含子查询SUBQUERY或者 UNION
PRIMARY 查询中若包含任何复杂的子部分,最外层查询则被标记为 Primary
(最后加载的鸡蛋壳🥚)
DERIVED 在 FROM 列表中包含的子查询被标记为 DERIVED(衍生) MySQL 会<u>递归执行</u>这些子查询, 把结果放在<u>临时表里</u>
SUBQUERY 在SELECT或WHERE列表中包含了子查询(带括号那些)
DEPEDENT SUBQUERY 在SELECT或WHERE列表中包含了子查询,子查询基于外层
UNCACHEABLE SUBQUERY 无法使用缓存的子查询
UNION 若第二个SELECT出现在UNION之后,则被标记为UNION; 若UNION包含在FROM子句的子查询中,外层SELECT将被标记为:DERIVED
UNION RESULT 从UNION表获取结果的SELECT

UNION 和 UNION RESULT


image-20200903180447715.png

3. table:输出结果集的表

显示这一行数据是关于哪张表的

4. type:表示表的连接类型

#是较为重要的一个指标,结果值从最好到最坏依次是:
system > const > eq_ref > ref > fulltext > ref_or_null 
> index_merge > unique_subquery > index_subquery > range > index > all

#常见的顺序
--一般来说,得保证查询至少达到 range 级别,最好能达到 ref
--查的少/细/精准    ---------->        查的多/广泛
system > const > eq_ref > ref > range > index > all 
类型名 含义
SYSTEM 表只有一行记录(等于系统表),这是 const 类型的特列,平时不会出现,这个也可以忽略不计
CONST 表示通过索引一次就找到了,const 用于比较 primary key 或者 unique 索引。因为只匹配一行数据,所以很快。如将主键置于 where 列表中,MySQL 就能将该查询转换为一个常量
EQ_REF 唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描
REF 非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,它返回所有匹配某个单独值的行, 然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体
RANGE 只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引一般就是在你的 where 语句中出现了 between、<>、in 等的查询这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束语另一点不用扫描全部索引
INDEX 出现index是sql使用了索引但是没用通过索引进行过滤,一般是使用了覆盖索引或者是利用索引进行了排序分组
ALL Full Table Scan,将遍历全表以找到匹配的行

5. possible_keys:表示查询时可能使用的索引

显示可能应用在这张表中的索引,一个或多个
查询设计到的字段上如果存在索引,则会被列出,但是不一定会被查询实际使用

6. key:表示实际使用的索引

实际使用的索引。如果为NULL,则没有使用索引

EXPLAIN SELECT * FROM tbl_emp WHERE id = 1 AND deptId = 1;
MySQL推测出可能使用到主键索引和fk_table_id外键部门编号索引,但是最后实际只用了主键索引
image-20200903195045228.png

7. 覆盖索引(数据在索引那,不要通过主键去回表)

mysql会判断你只要查找索引里面有的字段,那么就全往索引里面去查了
当查找的字段与建立索引的匹配(查找的字段都是索引,但是不需要整表的所有字段都有索引)
这个时候,就会发生覆盖索引,MySQL推测使用的索引为NULL,而实际上会使用索引
解释:

  1. select的数据列只要能从索引中获取,就不必从数据表中读取,查询列要被所使用的的索引覆盖
  2. 注意:要使用覆盖索引,**只取出需要的列(有创建索引),不要使用select ***


    image-20200903200703723.png

8.key_len:索引字段的长度

表示索引中使用的字节总数,key_len能够帮你检查是否充分利用上了索引
key_len越长,说明索引使用的越充分


image-20200903223847021.png
索引字段类型 索引占多少字节 备注
允许为null +1字节 MySQL需要1个字节标识NULL
char(20) 20*3 utf8字符集占用3个字节
varchar(20) 20*3 + 2 utf8字符集占用3个字节,变长字段需要+2。
timestamp 4 timestamp占用4字节

9. ref:列与索引的比较

ref显示索引的哪一列被使用了,如果可能的话,可以是一个常量

image-20200904070558202.png

10. rows:扫描出的行数(估算的行数)

rows列显示MySQL认为它执行查询时必须检查的行数,越少越好

--查询下索引名
show index from tbl_emp;
--先删除索引
DROP INDEX fk_table_id ON tbl_emp;
--查找
EXPLAIN SELECT * FROM tbl_emp,tbl_dept WHERE tbl_emp.deptId = tbl_dept.id;
--再创建索引
CREATE INDEX fk_table_id ON tbl_emp(deptId);
--查找
EXPLAIN SELECT * FROM tbl_emp,tbl_dept  WHERE tbl_emp.deptId = tbl_dept.id;
image-20200904074431108.png

11. Extra:执行情况的描述和说明

using filesort:使用外部索引排序(未使用用户创建的索引)----九死一生

  1. mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取,Mysql无法利用索引完成的排序称为"文件排序"
  2. 出现Using filesort说明SQL语句设计不好,没有按照创建的索引排序, 或者未按照索引指定的顺序进行排序
  3. idx_emp_empno_age,想要利用索引进行排序,中间不能断,ORDER BY age就会出问题
    image-20200904084314101.png

using-temporary:排序时使用临时表---------无一生还

  1. 使用了临时表保存中间结果,MySQL对查询结果排序时使用临时表,常见于排序order by或者group by
  2. 出现using temporary说明SQL语句设计的非常不好+1,可能是没有按照顺序使用索引排序


    image-20200904144111650.png

using index:使用了覆盖索引(Covering Index),效率不错

  1. 表示在select操作中使用的覆盖索引,避免访问了表的数据行,效率得到大幅提升
  2. 如果同时出现using where,表明索引被用来执行索引键值的查找(还行)
  3. 如果只出现using index,表明索引只是用来读取数据,而非利用索引来执行查找(效率更猛,索引上数据直接拿来用了!!!)(等于覆盖索引)
  4. Using join buffer:改值强调了在获取连接条件时没有使用索引,并且需要连接缓冲区来存储中间结果。如果出现了这个值,那应该注意,根据查询的具体情况可能需要添加索引来改进能。
  5. Impossible where:这个值强调了where语句会导致没有符合条件的行(通过收集统计信息不可能存在结果)。
  6. Select tables optimized away:这个值意味着仅通过使用索引,优化器可能仅从聚合函数结果中返回一行
  7. No tables used:Query语句中使用from dual 或不含任何from子句

五、单表查询优化(索引失效)

1.全值匹配很快捷

ALTER TABLE staffs ADD INDEX index_staffs_nameAgePos(`name`,`age`,`pos`);
EXPLAIN SELECT * FROM staffs WHERE NAME = 'July' AND age = 25 AND pos = 'dev';

2.最佳左前缀

建立了个复合索引(name,age,pos)
按照顺序,索引没问题

--查询
EXPLAIN SELECT * FROM staffs WHERE NAME = 'July';
EXPLAIN SELECT * FROM staffs WHERE NAME = 'July' AND age = 25;
EXPLAIN SELECT * FROM staffs WHERE NAME = 'July' AND age = 25 AND pos = 'dev';

不按顺序,索引出问题

--条件跳过了name
EXPLAIN SELECT * FROM staffs WHERE age = 25 AND pos = 'dev';
EXPLAIN SELECT * FROM staffs WHERE pos = 'dev';      

总结

  • 以上得出,查询的字段与索引字段的顺序不同,导致索引失效
  • 原因:使用复合索引,如果索引了多列,要遵守最左前缀法则,指的是查询从索引最左前列开始并且不跳过索引的列
  • 结论:过滤条件where使用索引必须按照建立索引的顺序,依次满足,一旦跳过某个字段,该字段后面的索引都无法使用

3.索引列上不计算

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

EXPLAIN SELECT * FROM staffs WHERE NAME = 'July';
EXPLAIN SELECT * FROM staffs WHERE LEFT(NAME,4) = 'July';
image-20200904232633429.png

4.范围之后全失效

EXPLAIN SELECT * FROM staffs WHERE NAME = 'July' AND age = 25 AND pos = 'dev';
EXPLAIN SELECT * FROM staffs WHERE NAME = 'July' AND age >25 AND pos = 'dev';
image-20200905070502640.png

总结

  • 建议:可以尽量将范围查找的字段的索引顺序放在最后面
  • 结论:使用范围查找后,范围内的记录过多,可能会导致全表扫描,因为自定义索引映射到主键索引需要消耗的时间太多,反而不如直接全表扫描来得快

5.覆盖索引多使用:不用select *

  1. 尽量使用覆盖索引,只按顺序查询复合索引的各个字段(查询列和索引列一致),减少出现select *
  2. 覆盖索引直接从索引里面取值,出现了using index,索引可以用来读取数据,不用利用索引进行查找
    image-20200905072819820.png

6.使用<>会失效:致扫全表

在使用不等于(!= 或者<>)时,有时会无法使用索引会导致全表扫描

7.使用NULL值要小心

IS NULL或者IS NOT NULL时,可能会导致索引失效

  • IS NULL 不会导致索引失效
  • IS NOT NULL 会导致索引失效


    image-20200905082808660.png

8.模糊查询加右边

要使用模糊查询时,百分号最好加在右边,而且进行模糊查询的字段必须是单值索引

--百分号最好加在右边
EXPLAIN SELECT * FROM staffs WHERE NAME like 'July';
EXPLAIN SELECT * FROM staffs WHERE NAME like '%July';
EXPLAIN SELECT * FROM staffs WHERE NAME like 'July%';
image-20200905085247322.png
  • 可以看出,索引为单值索引且模糊查询语句在最右边时,索引才会生效
  • 其他情况均失效了(%string || %string%),但是有时必须使用%string%,那么就需要用覆盖索引来解决问题了

9.字符串加单引号

  1. 当where字段为字符串varchar时,查询时必须带上<u>单引号</u>。否则底层会发生自动的类型转换,索引失效从而触发全表扫描

    整型转为varchar是严重错误的
    底层原理,varchar类型的name = 2000,为了使用索引来快速查找2000,那么原本是varchar索引树就会重构,但是索引树类型重构varchar -> int的代价是非常大的,mysql会认为直接全表扫描快点
    varchar转为整型规则(mysql自动识别)
    除数字的字符类保留,其他通通转成0
    int a = 'a',则mysql会自动优化成a = 0
    int a = '1000' a=1000

  2. 不加单引号'',导致发生自动的类型转换致使索引失效


    image-20200905094804587.png

10.尽量不用or查询

  1. 如果使用or,可能导致索引失效。所以要减少or的使用,可以使用 union all 或者 union 来替代:
  2. or导致索引失效


    image-20200905095513543.png

11.总结补充

巧记(select 字段 where a = 1 and b = 1)

  1. 覆盖不用select *
  2. 最左前缀顺序-头不死--中不断
  3. 索引列少计算和强转(left() || int->varchar)
  4. 范围不要,范围后都失效
    • or,exist,in,<>,!=
    • not null
    • like
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容