【MySQL核心面试题】MySQL 核心 - Explain 执行计划详解!

欢迎关注公众号 【11来了】(文章末尾即可扫码关注) ,持续 中间件源码、系统设计、面试进阶相关内容

在我后台回复 「资料」 可领取 编程高频电子书!
在我后台回复「面试」可领取 30w+ 字的硬核面试笔记!
示例图片
感谢你的关注!

该文章内容已经收录在《面试进阶之路》,包含底层原理解析,带你冲破面试迷雾

了解 Explain 执行计划吗?

为什么要知道 SQL 的执行计划?

首先我们为什么要去学习 MySQL 中的执行计划呢?

我们知道,在大多数场景下对于 MySQL 的优化都是通过建立索引来完成的,但是在实际业务场景中,可能一个 SQL 非常复杂,其中执行起来我们可能并不能去预测它的执行方式,也不知道它到底走没有走索引

那么就可能出现我们去针对这个 SQL 建立了联合索引,但是性能还是非常差的情况,因此针对这种情况,我们需要去查看 SQL 的执行计划,了解 SQL 是如何运行的,之后再针对它进行索引、性能优化!

详解执行计划 type 列

通过 explain 语句可以帮助我们查看查询语句的具体执行计划,那么在执行计划中的 type 列表示 MySQL 是如何查找对应的数据了,我们先来说一下执行计划中 type 列常见的值

这里主要说一下常见的几种:const、ref、range、index,性能从左到右逐渐变差

  • 首先,const 的话表示性能是常量级的,非常快

就比如对于 SQL 语句:select * from table where id = 1

SQL 语句可以通过 聚簇索引 或者 二级唯一索引 + 聚簇索引 的方式,在常量级别的时间内找到我们想要的数据

这里需要注意的是,如果使用的是二级唯一索引的方式,必须保证建立 unique key 唯一索引,来保证二级索引列中的值都是唯一的,比如对于 SQL:select * from table where name = x ,那么就需要保证 name 列的值是唯一的,且 name 列是二级索引

1707581883781

  • ref 级别的查询

如果在 SQL 中只使用到了普通的二级索引,如:select * from table where name = x ,name 为普通二级索引,不是唯一索引,那么此时 SQL 的查询级别就是 ref,速度也比较快

如果对聚簇索引或者唯一索引判断是否为 null 值的话,也是 ref 级别的查询,如:select * from table where name is NULL ,如果 name 是聚簇索引(主键索引)或者唯一索引的话,此时查询级别为 ref

1707583045414

  • range 级别的查询

使用索引进行范围查询的 SQL,此时就是 range 级别的查询

如:select * from table where age >= 18 and age <= 30 ,如果 age 为普通索引,通过 age 进行范围查询,则查询为 range 级别的

  • index 级别的查询

看到 index 级别,可能觉得很快,其实不是这样的,index 级别的 SQL 查询性能仅仅比全表扫描要略好一些而已

index 的意思就是,如果有一个 SQL,发现你建立了一个联合索引,这个索引恰好是覆盖索引,因此直接遍历这个联合索引的叶子节点,将要查询的值全部取出来之后,就不需要再去聚簇索引中取值了,这种情况下查询的级别就是 index,性能仅仅比全表扫描要好一些而已

比如说 select account, name, age from table where name = x ,建立的联合索引为(account, name, age),那么发现 where 条件中直接根据 name 判断,不符合最左前缀原则,但是符合覆盖索引,因此 MySQL 判断二级索引大小还是比较小的,因此直接扫描二级索引的全部叶子节点,直接将对应的值给取出来即可

1707583100390

explain 各列含义

explain 查出来的各列含义如下:

  • id:在一个大的查询语句中,每个 select 关键字都对应一个唯一的 id

  • select_type:select 关键字对应的那个查询的类型

    • simple:简单查询。表示查询不包含子查询和union

    • primary:复杂查询中最外层的 select

    • subquery:包含在 select 中的子查询(不在 from 子句中)

    • derived:包含在 from 子句中的子查询。MySQL会将结果存放在一个临时表中,也称为派生表(derived的英文含义)

      set session optimizer_switch='derived_merge=off';  #关闭 mysql5.7 新特性对衍生表的合并优化
      explain select (select 1 from employees where id = 5) from (select * from account where id = 3) der;
      set session optimizer_switch='derived_merge=on'; #还原默认配置
      

      1698718620195

    • union:在 union 中的第二个和随后的 select

      explain select 1 union all select 1;
      
  • partitions:匹配的分区信息

  • type:表示访问类型,即 MySQL 决定如何查找表中的行。从最优到最差分别为:system > const > eq_ref > ref > range > index > ALL

    一般来说得保证查询达到 range 级别,最好达到 ref

    • Null:表示 MySQL 在优化阶段分解查询语句,执行时不需要再访问表或索引。例如 explain select min(id) from account; 在索引列中取最小值,单独查询索引即可,执行时不需要再访问表

    • system:当表中只有一条记录并且该表使用的存储引擎的统计数据是精确的,比如 explain select * from test;test 表中只有一条数据,如果 test 表使用 MyISAM 存储引擎,则 type 为 system;如果 test 表使用 InnoDB 存储引擎,则 type 为 ALL

    • const:const 表示代价时常数级别,当根据主键索引、唯一索引、二级索引与常数进行等值匹配时,对单表访问就是 const,只匹配到一行数据,很快.

      explain select * from account where id = 1

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

      explain select * from t1 left join t2 on t1.id=t2.id

    • ref:相比于 eq_ref,不使用唯一索引,使用普通索引或者唯一索引的部分前缀,索引值和某个值相比较,可能找到多个符合条件的行

      name 是普通索引(非唯一索引),explain select * from account where name = 'abc'

    • range:范围扫描通常出现在 in()between><等操作

    • index:扫描全索引就能拿到结果,一般是扫描某个二级索引,会直接对二级索引的叶子节点遍历和扫描。这种查询一般为使用了覆盖索引,二级索引比较小,所以通常比 ALL 快一些

    • ALL:全表扫描,扫描聚簇索引的所有叶子节点,通常情况下这需要增加索引来进行优化

  • possible_keys:可能用到的索引

    查询时可能出现 possible_keys 有列,但是 key 显示 Null 的情况,这是因为表中数据不多,MySQL 认为索引帮助不大,选择了全表扫描

    如果该列是 Null,说明没有相关索引,可以通过添加索引来提高查询性能

  • key:实际上使用的索引

    如果为 Null 表示没有使用索引,可以使用 force indexignore index 来强制使用索引

  • key_len:实际使用到的索引长度

    key_len 计算规则如下:

    • 字符串,char(n)、varchar(n) 在 5.0.3 之后,n 代表字符数,而不是字节数,如果是 utf-8,一个数字或字母占 1 个字节,1 个汉字占 3 个字节
      • char(n):如果存汉字,长度为 3n 字节
      • varchar(n):
        • 如果存汉字(utf8),长度为 3n+2 字节,加的 2 字节用来存储字符串长度(varchar 是变长字符串)
        • 如果存汉字(utf8-mb4),长度为 4n+2 字节
    • 数值类型
      • tinyint:1 字节
      • smallint:2 字节
      • int:4 字节
      • bigint:8 字节
    • 时间类型:
      • date:3 字节
      • timestamp:4 字节
      • datetime:8 字节
    • 如果字段允许为 Null,则还需要 1 字节记录是否为 Null

    计算示例:

    • 设置索引:idx_balance(balance),SQL 语句为 explain select name from account where balance = '111' ;

      该 SQL key_len = 5,4 个字节用于存储 balance(int,4B),1 个字节记录是否为 Null

    • 设置索引:idx_name(name),name 字段编码为 uft8-mb4,长度为varchar(10),explain select name from account where name = 'abc';

      该 SQL key_len = 43,name 索引长度为 10,使用 utf8-mb4 存储汉字的话,1 个汉字占 4 个字节,长度为 10 所占用字节为 4 * 10 = 40,还需要 2 个字节存储 varchar 的长度,name 字段可以为空,因此还需要 1 个字节记录是否为 Null,因此 name 索引的长度为 40 + 2 + 1 = 43

      如果是 utf-8 编码,1 个汉字是占 3 个字节的。

  • ref:当使用索引列等值查询时,与索引列进行等值匹配的对象信息,常见的 ref 值有:const(常量),字段名(例如:film.id)

  • rows:预估的需要读取的记录条数,并不是结果集中的实际行数

  • Extra:—些额外的信息,常见的重要值如下:

    • Using index:使用覆盖索引,通过索引可以直接获取查询结果,无需回表查询

    • Using where:使用 where 语句来处理结果,并且查询的列未被索引覆盖

    • Using index condition:查询的列不完全被索引覆盖,where 条件中是一个前导列的范围

      • 示例:索引(name,balance) explain select *from account where name > 'a';
    • Using temporary:mysql 需要创建一张临时表来处理查询。出现这种情况需要使用索引进行优化

      • 示例:name 字段没有索引,此时创建一张临时表来 distinct,explain select distinct name from account

      临时表可能在内存上,也可能在磁盘上,这取决于 tmp_table_sizemax_heap_table_size 值的最小值,内存临时表的最大容量为这两个参数中的最小值,可以通过命令查看:show global variables like 'tmp_table_size'show global variables like 'max_heap_table_size'

    • Using filesort:使用外部排序而不是索引排序,数据较少时在内存中排序,数据较大时在磁盘中排序,一般情况下也是需要考虑使用索引进行优化

      • 示例:name 字段没有索引,explain select name from account order by name

      一般在没有索引的列上进行 order by 就会触发 Using filesort,可以在 order by 的列上添加索引

      如果 order by 是多字段排序,也要遵循最左前缀原则,并且都是升序或者降序

      如果不可避免地要出现 Using filesort,可以适当增大排序缓冲区大小 sort_buffer_size(默认 256k):show variables like 'sort_buffer_size'

      当排序的数据大小 < sort_buffer_size 时,为内存排序;否则为磁盘排序

    • Select tables optimized away:使用聚合函数来访问存在索引的某个字段

      • 示例:explain select min(id) from account;

通过实操理解 explain 执行计划

  • 案例一:开胃小菜

SQL 语句:

explain select * from test1;

执行计划如下:

image-20240212195020126

首先,id = 1,id 是每一个 SQL 语句的唯一标识

select_type 值为 SIMPLE 表示这个 SQL 是一个简单的查询,不包含子查询以及 union 等操作

table 表明对哪个表进行的操作

type = index 表明对二级索引的叶子节点进行扫描得到了结果,因为这个 test1 表里只有两个字段,id 和 name,我在 name 列上建立了索引,因此对 name 索引的叶子节点扫描一遍就可以得到 id 和 name 值

rows = 3 表明扫描了 3 行数据

filtered = 100 表明没有通过 where 过滤数据,因此筛选出来的数据在表里数据的占比为 100%

  • 案例二:多表查询

SQL 语句:

explain select * from test1 join test2;

执行计划:

image-20240212195845525

可以发现有两条执行计划,也就是说明会访问两个表,两条执行计划的 id 都是 1,说明是同一个 SQL 语句

首先第一条执行计划是对 test2 表进行全表扫描(type = ALL),rows = 1 表明扫描出来了 1 条数据,在表中占比为 100%

第二条执行计划是对 test1 表进行全表扫描,rows = 3 表明扫描出来 3 条数据,占比 100%,其中 Extra 列与第一条执行计划有所不同

可以看到 Extra 列值为 Using join buffer(Block Nested Loop)

这是因为使用了 join 对两个表进行连表查询,这样其实查出来的是笛卡尔积,对两个表中的所有数据进行关联,在 MySQL 中一般会以数据量比较小的表作为驱动表,因此以 test2 表为驱动表,去 test1 表中找到所有数据进行匹配,小表作为驱动表可以减少比较的行数,在 test1 表中对数据进行匹配时使用到了 Using join buffer,也就是通过一块内存区域 join buffer 来对数据进行连接操作,而 Nested Loop 表明进行嵌套循环连接,也就是笛卡尔积(test2 表的每一行数据都和 test1 表的每行数据做连接)

  • 案例三:union 并集查询

SQL 语句:

explain select * from test1 union select * from test2;

执行计划:

image-20240212201840172

前两条执行计划就是对 test1 和 test2 这两张表进行全表扫描操作

第 3 条执行计划是对两张表中的数据进行合并去重操作,table = <union 1,2> 指的是这个临时表的表名,extra = Using temporary 也表明了使用了临时表

union 是对两张表的结果进行合并去重

union all 的话不会对数据进行去重操作

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/57130.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL【知识改变命运】10

联合查询 0.前言1.联合查询在MySQL里面的原理2.练习一个完整的联合查询2.1.构造练习案例数据2.2 案例&#xff1a;⼀个完整的联合查询的过程2.2.1. 确定参与查询的表&#xff0c;学⽣表和班级表2.2.2. 确定连接条件&#xff0c;student表中的class_id与class表中id列的值相等2.…

wordpress 子比主题美化 四宫格 多宫格 布局插件

wordpress 主题美化 四宫格 多宫格 布局插件&#xff08;只在子比主题上测试过&#xff0c;其它主题没测试&#xff09; A5资源网四宫格布局插件是一个功能丰富的WordPress插件,专为创建自适应的四宫格布局而设计。这个插件具有以下主要特点: 灵活的布局: 支持1到8个宫格的自定…

Springboot整合knife4j生成文档

前言 在开发过程中&#xff0c;接口文档是很重要的内容&#xff0c;用于前端对接口的联调&#xff0c;也用于给其他方使用。但是手写相对比较麻烦。 当然也有swagger之类的&#xff0c;但是界面没有那么友好。 官网&#xff1a; 整合步骤 整合依赖 需要根据版本进行&…

如何使用 pnpm 进行打补丁patch操作?推荐两个方法

前言 作为一个前端开发者&#xff0c;我们每天都在和各种各样的库和依赖打交道。node_modules 目录中存放着我们项目的各种依赖。我们有时需要对其中的一些依赖进行修改&#xff0c;比如修复某个 bug 或者增加某些自定义功能。这时候&#xff0c;给 node_modules 打补丁就显得…

为您的 WordPress 网站打造完美广告布局 A5广告单元格插件

一个为 WordPress 网站量身定制的强大工具,它将彻底改变您展示广告的方式 灵活多变的布局设计 A5 广告单元格插件的核心优势在于其无与伦比的灵活性。无论您是想要创建整齐的网格布局,还是希望打造独特的不规则设计,这款插件都能满足您的需求。 自定义网格数量&#xff1a;从 2…

androidStudio编译导致的同名.so文件冲突问题解决

files found with path lib/arm64-v8a/libserial_port.so from inputs: ...\build\intermediates\library_jni\debug\jni\arm64-v8a\libserial_port.so C:\Users\...\.gradle\caches\transforms-3\...\jni\arm64-v8a\XXX.so 解决方式如下&#xff1a; 1.将gradle缓存文件删…

TwinCAT3安装 Advanced Motion Pack库

文章目录 一.简介二.安装方式1. 下载地址2. 双击下载好的安装包3. 选择语言&#xff08;只有英文和德语&#xff09;4. 点击Next5. 选择Accept6. 填写公司和组织名称&#xff08;随意&#xff09;7. 点击Install8. 等待安装完成9. 点击Finish 一.简介 TF5420 TC3 Motion Pick-…

深度学习-1:逻辑回归和梯度下降

逻辑回归 逻辑回归是一个二分分类问题 比如判断一张图片中是否是猫就是一个二类分类问题 图像由像素值组成&#xff0c;要将图像输入模型&#xff0c;就将其变为一个向量&#xff0c;该向量存储三个通道上的所有像素值&#xff0c;若图像尺寸为64x64x3&#xff0c;则向量维度…

RequestBody接收参数报错com.fasterxml.jackson.databind.exc.MismatchedInputException

目录&#xff1a; 1、错误现象2、解决办法3、最终验证 1、错误现象 报错的现象和代码如下&#xff1a; 2、解决办法 查了很多都说参数类型对不上&#xff0c;但是明明是对上的&#xff0c;没有问题&#xff0c;最后只有换接收方式后验证是可以的&#xff1b;最终想了一下&…

Unity3D 观察者模式

Unity3D 泛型事件系统 观察者模式 观察者模式是一种行为设计模式&#xff0c;通过订阅机制&#xff0c;可以让对象触发事件时&#xff0c;通知多个其他对象。 在游戏逻辑中&#xff0c;UI 界面通常会监听一些事件&#xff0c;当数据层发生变化时&#xff0c;通过触发事件&am…

网络参考模型总结

物理层&#xff1a; 电信号&#xff0c;有中继器、集线器等设备。 数据链路层&#xff1a;帧&#xff0c;有网桥、交换机等设备&#xff0c;通过mac地址。 网络层&#xff1a;包&#xff0c;有路由器等设备&#xff0c;通过IP地址。 传输层&#xff1a;段&#xff0c;有网…

rootless模式下istio ambient的流量管理测试

接上一篇rootless模式下istio ambient鉴权策略&#xff0c;本次测试管理流量的功能。 服务流量分割 Bookinfo应用程序有三个版本的reviews服务&#xff0c;接下来对这些版本进行分配流量控制测试。 longtdsubuntu:~$ kubectl get pod |grep reviews reviews-v1-746f96c9d4-2…

出类拔萃的四款录屏工具你pick哪一个?

在这个屏幕时代&#xff0c;录屏已经成为我们日常工作和娱乐中不可或缺的一部分。无论是为了记录游戏精彩瞬间&#xff0c;还是为了分享教程和经验&#xff0c;一个好的录屏工具都是必不可少的。今天&#xff0c;就让我为大家推荐几款电脑免费录屏工具&#xff0c;并分享一下使…

DDD通用语言、多尿和尿频-《分析模式》漫谈41

“Analysis Patterns”的第3章有这么一句&#xff1a; A patient with observations of the presence of thirst, weight loss, and polyuria indicates diabetes. 2004&#xff08;机械工业出版社&#xff09;中译本的译文为&#xff1a; 虽然没有完全使用类图&#xff08;类…

【C++_string类练习】仅仅反转字母

题目链接&#xff1a;仅仅反转字母 解题思路&#xff1a; 这种反转字符的题目我第一个想到的方法就是&#xff1a;双指针 一个指针在前start&#xff0c;一个指针在后back&#xff0c; 如果指针所指向的位置的值是字母&#xff0c;那么两个指针位置的值就进行交换&#xff0…

矿山企业电能智能化管理怎么实现

0引言 电力工业是国民经济和社会发展的基础&#xff0c;随着高质量发展要求的提升&#xff0c;提高能源效率对工业企业至关重要。有效提高电力能效的方法包括技术创新、监测用能行为和探索技术解决方案。智能化的电能管理是应对资源和环境压力的关键措施&#xff0c;对提高用电…

点餐小程序实战教程20广告管理

目录 1 创建数据源2 添加轮播容器3 创建变量4 绑定变量5 预览应用总结 一般餐厅需要有一些宣传&#xff0c;在我们的点餐页面可以在顶部加载广告位。广告主要是用轮播图的形式进行展示&#xff0c;本节我们介绍一下如果显示广告。 1 创建数据源 打开控制台&#xff0c;点击应用…

10-Docker安装Redis

10-Docker安装Redis Docker安装Redis 以 Redis 6.0.8 为例&#xff1a; docker pull redis:6.0.8直接pull会出现以下错误 [rootdocker ~]# docker pull redis:6.0.8 Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request can…

【Cadence27】HDL拷贝工程➕Allegro导出DXF和3D文件STP

【转载】Cadence Design Entry HDL 使用教程 【Cadence01】Cadence PCB Edit相对延迟与绝对延迟的显示问题 【Cadence02】Allegro引脚焊盘Pin设置为透明 【Cadence03】cadence不小心删掉钢网层怎么办&#xff1f; 【Cadence04】一般情况下Allegro PCB设计时的约束规则设置&a…

卸载 Git

目录 打开 Geek找到 Git右键卸载 Git卸载完成等待扫描注册表点击完成清除Git在本地的配置文件 打开 Geek 找到 Git 右键卸载 Git 卸载完成 等待扫描注册表 点击完成 已经删除完毕 清除Git在本地的配置文件 进入 C:\Users\用户名 删除框起来的配置文件 &#x1f447; 源码资料…