Mysql数据查询优化——sql执行顺序&执行计划

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: Mysql数据查询优化——sql执行顺序&执行计划

正文


sql语句的执行顺序


Mysql的执行流程图如下 图片来自网络


222.png


连接器


连接器就是起到连接的作用,主要职责有


1、验证请求用户的账户和密码是否正确。


2、用于客户端的通信。Mysql的TCP协议是一个半双工通信模式因此在某一固定时刻只能由客户端向服务器请求或者服务器向客户端发送数据,而不能同时进行。


单工: 数据传输只允许在一个方向上的传输,单向传输,只能一方来发送数据,另一方来接收数据并发送。例如:遥控器。

半双工:数据传输允许两个方向上的传输,但是同一时间内,只可以有一方发送或接受消息。例如:打电话

全双工:同时可进行双向传输。例如:websocket、Http2.0。

3、如果账号密码验证通过,会在mysql自带的权限表中验证当前用户权限。mysql库中有4个控制权限的表,分别为user表,db表,tables_priv表,columns_priv表。


1)user表存放用户账户信息以及全局级别(所有数据库)权限,决定了来自哪些主机的哪些用户可以访问数据库实例。


333.png


2)db表存放数据库级别的权限,决定了来自哪些主机的哪些用户可以访问此数据库。%同样表示所有的主机可连。


444.png

3)Tables_priv表:存放表级别的权限,决定了来自哪些主机的哪些用户可以访问数据库的这个表。


4)Columns_priv表:存放列级别的权限,决定了来自哪些主机的哪些用户可以访问数据库表的这个字段。


验证过程如下:


先从user表中的Host,User,Password这3个字段中判断连接的ip、用户名、密码是否存在,存在则通过验证。

通过身份认证后,进行权限分配,按照user、db、table、 columns依次判断,如果user表中全局变量都是Y则不再进行下面的判断,否则一步一步判断权限。

如果在任何一个过程中权限验证不通过,都会报错。


缓存


mysql的缓存主要的作用是为了提升查询的效率,缓存以key和value的哈希表形式存储,key是具体的sql语句,value是结果的集合。如果无法命中缓存,就继续走到分析器,如果命中缓存就直接返回给客户端 。不过需要注意的是在mysql的8.0版本以后,缓存被官方删除掉了。之所以删除掉,是因为查询缓存的失效非常频繁,如果在一个写多读少的环境中,缓存会频繁的新增和失效。


需要注意 :缓存和哈希自适性索引的区别,自适性哈希是通过哈希表实现的,它是数据库自身创建的不能人为的创建和删除。通过一下sql可以查看。


SHOW ENGINE INNODB STATUS ;
SHOW ENGINE INNODB STATUS \G; #cmd窗口使用这个自动分行

222.png


分析器


分析器的主要作用是将客户端发过来的sql语句进行分析,这将包括预处理与解析过程,在这个阶段会解析sql语句的语义,并进行关键词(select、update、delete、where、order by、group by等等)和非关键词进行提取、解析,并组成一个解析树。另外在此过程还会对sql语法进行分析,除此之外还会校验表是否存在,表中的字段值是否存在。下面是一个解析树

222.png


优化器


能够进入到优化器阶段表示sql是符合mysql的标准语义规则的并且可以执行的,此阶段主要是进行sql语句的优化,会根据执行计划进行最优的选择,匹配合适的索引,选择最佳的执行方案。如MRR(Multi-Range Read 多范围读取)优化,ICP(Index Condition Pushdown 索引下推)优化,是否选择使用索引,选择使用主键索引还是其他索引等。


执行器


在执行器的阶段,此时会调用存储引擎的API,API会调用存储引擎。下面罗列几个存储引擎。


111.png


存储引擎是基于表的,而不是数据库。使用下面sql可以查看mysql支持的存储引擎


SHOW ENGINES;


333.png


Sql执行顺序


实际上sql语句并不是按照我们写的sql的顺序从左到右依次执行的,它是按照如下顺序执行的。


222.png


from 第一步就是选择出from关键词后面跟的表,这也是sql执行的第一步:表示要从数据库中执行哪张表。


join on join是表示要关联的表,on是连接的条件。通过from和join on选择出需要执行的数据库表t1和t2产生笛卡尔积,生成t1和t2合并的临时中间表Temp1。on:确定表的绑定关系,通过on产生临时中间表Temp2。


where where表示筛选,根据where后面的条件进行过滤,按照指定的字段的值(如果有and连接符会进行联合筛选)从临时中间表Temp2中筛选需要的数据,注意如果在此阶段找不到数据,会直接返回客户端,不会往下进行.这个过程会生成一个临时中间表Temp3。注意在where中不可以使用聚合函数,聚合函数主要是(min、max、count、sum等函数)。


group by group by是进行分组,对where条件过滤后的临时表Temp3按照固定的字段进行分组,产生临时中间表Temp4,这个过程只是数据的顺序发生改变,而数据总量不会变化,表中的数据以组的形式存在。


having 对临时中间表Temp4进行聚合,然后产生中间表Temp5,在此阶段可以使用select中的别名。


select 对分组聚合完的表挑选出需要查询的数据,如果为*会解析为所有数据,此时会产生中间表Temp6。


distinct distinct对所有的数据进行去重,此时如果有min、max函数会执行字段函数计算,然后产生临时表Temp7。


order by 会根据Temp7进行顺序排列或者逆序排列,然后插入临时中间表Temp8,这个过程比较耗费资源。


limit limit对中间表Temp8进行分页,产生临时中间表Temp9,返回给客户端。


实际上这个过程也并不是绝对这样的,中间mysql会有部分的优化以达到最佳的优化效果,比如在select筛选出找到的数据集。


执行计划


下面所有的sql语句是在Mysql 8.0.27版本上执行的。两张表,表结构为


CREATE TABLE `tb_salary`  (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `user_id` int NULL DEFAULT NULL COMMENT '用户id',
  `salary` decimal(10, 2) NOT NULL COMMENT '工资',
  `salary_time` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '薪资月份',
  `create_time` datetime NULL DEFAULT NULL,
  `update_time` datetime NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '薪资表' ROW_FORMAT = Dynamic;
CREATE TABLE `tb_user`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '名字',
  `age` int NULL DEFAULT NULL,
  `position` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '职务',
  `address` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '地址',
  `create_time` datetime NULL DEFAULT NULL,
  `update_time` datetime NULL DEFAULT NULL,
  `delete_flag` tinyint NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 100 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;


explain


执行如下sql


EXPLAIN SELECT t2.*,( SELECT 1 FROM tb_user WHERE id = 100000 ) 
FROM
  tb_user t1
  LEFT JOIN tb_salary t2 ON t1.id = t2.user_id 
WHERE
  t2.id IS NOT NULL;


执行计划结果如下


770259d1a417490293a94d9fc2494375.png


字段说明


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


select: 查询类型


(1) SIMPLE(简单SELECT,不使用UNION或子查询等)。


(2) PRIMARY(子查询中最外层查询,查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY)。


(3) UNION(UNION中的第二个或后面的SELECT语句)。


(4) DEPENDENT UNION(UNION中的第二个或后面的SELECT语句,取决于外面的查询)。


(5) UNION RESULT(UNION的结果,union语句中第二个select开始后面所有select)。


(6) SUBQUERY(子查询中的第一个SELECT,结果不依赖于外部查询)。


(7) DEPENDENT SUBQUERY(子查询中的第一个SELECT,依赖于外部查询)。


(8) DERIVED(派生表的SELECT, FROM子句的子查询)。


(9) UNCACHEABLE SUBQUERY(一个子查询的结果不能被缓存,必须重新评估外链接的第一行)。


派生查询例子


#派生表的优化合并,默认是开启的,需要手动关闭。
set session optimizer_switch='derived_merge=off';
EXPLAIN SELECT
  ( SELECT id FROM tb_salary WHERE id = 1 ) 
FROM
  ( SELECT * FROM tb_user WHERE id = 95885 ) t1;


444.png


其中table中的3指向的是id列的值。


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


partitions:查询是基于分区表的话,会显示查询将访问的分区,mysql5.6版本之后才有。


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


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


EXPLAIN SELECT
  ( SELECT id FROM tb_salary WHERE id = 1 ) 
FROM
  ( SELECT * FROM tb_user WHERE id = 95885 ) t1;


111.png


(2) eq_ref:使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用primary key或者 unique key作为关联条件。


EXPLAIN SELECT
  t2.*
FROM
  tb_user t1
  LEFT JOIN tb_salary t2 ON t1.id = t2.user_id 
WHERE
  t2.id IS NOT NULL;


111.png


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


EXPLAIN SELECT
  t2.*
FROM
  tb_user t1
  LEFT JOIN tb_salary t2 ON t1.id = t2.user_id 
WHERE
  t2.salary=10500.00


8074245061964fd5a3b4850740109991.png


其中salary字段上有普通索引。


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


EXPLAIN SELECT * FROM tb_user WHERE id <=95908


111.png


(5) index  : 只遍历索引树就能拿到结果,一般是扫描某个二级索引,这种扫描不会从索引树根节点开始快速查找,而是直接 对二级索引的叶子节点遍历和扫描,速度还是比较慢的,这种查询一般会使用覆盖索引,二级索引一般比较小,所以这 种通常比ALL快。


首先对上面的表创建索引


-- 创建索引
CREATE INDEX idx_name_age_position ON tb_user(name,age,position);
-- 前15个字符创建索引
ALTER TABLE tb_user ADD INDEX address (address(15));
-- 创建聚簇索引
CREATE CLUSTERED INDEX 索引名 ON 表名(字段名);
-- 删除索引
DROP INDEX idx_name_age_position ON tb_user;
-- 删除索引
ALTER TABLE tb_user DROP INDEX idx_name_age_position;
-- 删除主键索引
ALTER TABLE tb_user DROP PRIMARY KEY;


EXPLAIN SELECT name,age,position FROM tb_user;


8074245061964fd5a3b4850740109991.png


(6)All: 即全表扫描,扫描你的聚集索引的所有叶子节点。通常情况下这需要增加索引来进行优化了。


EXPLAIN SELECT * FROM tb_user;


444.png


(7)null : mysql能够在优化阶段分解查询语句,在执行阶段用不着再访问表或索引。


EXPLAIN SELECT MAX(id) FROM tb_user;


222.png


possible_keys:可能用到的索引。


key: 实际用到的索引,实际情况中有可能possible_keys有值,而实际执行时候key没有值,这种情况下可能是mysql优化器觉得全表扫描比使用索引查询效率要高,而没有使用索引。如果强制mysql使用索引,则可以使用force index(索引名称)来实现。


EXPLAIN SELECT
  * 
FROM
  tb_salary FORCE INDEX ( salary ) 
WHERE
  salary = 15000;


key_len: 这一列显示了mysql在索引里使用的字节数,通过这个值可以算出具体使用了索引中的哪些列。

key_len计算规则如下:


字符串,char(n)和varchar(n),5.0.3以后版本中,n均代表字符数,而不是字节数,如果是utf-8,一个数字或字母占1个字节,一 个汉字占3个字节。

char(n):如果存汉字长度就是 3n 字节,固定长度。

varchar(n):如果存汉字则长度是 3n + 2 字节,加的2字节用来存储字符串长度,因为 varchar是变长字符串。


数值类型


tinyint:1字节

smallint:2字节

int:4字节

bigint:8字节


时间类型


date:3字节

timestamp:4字节

datetime:8字节


如果字段允许为 NULL,需要1字节记录是否为 NULL。索引最大长度是768字节,当字符串过长时,mysql会做一个类似左前缀索引的处理,将前半部分的字符提取出来做索引。


EXPLAIN SELECT name,age,position FROM tb_user;


333.png


name为varchar(20) 允许空值(1个字节)   20*3+2+1=63


age 为int  允许空值 4+1=5


position为varchar(50)  50*3+2+1=153


则 63+5+153=221


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


rows: 预估数据行数,并不一定等于查询的返回结果。


filtered:符合某条件的记录数百分比。


Extra:


Using where——表示MySQL将对存储引擎层提取的结果进行过滤,过滤条件字段无索引, Using where本身其实和是否使用索引无关。


Using index——表示使用覆盖索引,查询的字段在覆盖索引中就可以获取到。


Using index condition——在5.6版本后加入的新特性(Index Condition Pushdown)后面具体说明。


Using filesort——表示没有使用索引的排序。


参考 :


https://www.cnblogs.com/wyq178/p/11576065.html


https://blog.csdn.net/admin522043032/article/details/121037081



相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
11天前
|
SQL 关系型数据库 MySQL
MySQL慢查询优化、索引优化、以及表等优化详解
本文详细介绍了MySQL优化方案,包括索引优化、SQL慢查询优化和数据库表优化,帮助提升数据库性能。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
MySQL慢查询优化、索引优化、以及表等优化详解
|
10天前
|
SQL 缓存 监控
大厂面试高频:4 大性能优化策略(数据库、SQL、JVM等)
本文详细解析了数据库、缓存、异步处理和Web性能优化四大策略,系统性能优化必知必备,大厂面试高频。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:4 大性能优化策略(数据库、SQL、JVM等)
|
19天前
|
SQL 存储 缓存
如何优化SQL查询性能?
【10月更文挑战第28天】如何优化SQL查询性能?
64 10
|
16天前
|
缓存 监控 关系型数据库
如何优化MySQL查询速度?
如何优化MySQL查询速度?【10月更文挑战第31天】
44 3
|
18天前
|
SQL 存储 缓存
SQL Server 数据太多如何优化
11种优化方案供你参考,优化 SQL Server 数据库性能得从多个方面着手,包括硬件配置、数据库结构、查询优化、索引管理、分区分表、并行处理等。通过合理的索引、查询优化、数据分区等技术,可以在数据量增大时保持较好的性能。同时,定期进行数据库维护和清理,保证数据库高效运行。
|
18天前
|
缓存 关系型数据库 MySQL
如何优化 MySQL 数据库的性能?
【10月更文挑战第28天】
42 1
|
25天前
|
NoSQL 关系型数据库 MySQL
MySQL与Redis协同作战:百万级数据统计优化实践
【10月更文挑战第21天】 在处理大规模数据集时,传统的单体数据库解决方案往往力不从心。MySQL和Redis的组合提供了一种高效的解决方案,通过将数据库操作与高速缓存相结合,可以显著提升数据处理的性能。本文将分享一次实际的优化案例,探讨如何利用MySQL和Redis共同实现百万级数据统计的优化。
67 9
|
19天前
|
监控 关系型数据库 MySQL
数据库优化:MySQL索引策略与查询性能调优实战
【10月更文挑战第27天】本文深入探讨了MySQL的索引策略和查询性能调优技巧。通过介绍B-Tree索引、哈希索引和全文索引等不同类型,以及如何创建和维护索引,结合实战案例分析查询执行计划,帮助读者掌握提升查询性能的方法。定期优化索引和调整查询语句是提高数据库性能的关键。
91 1
|
25天前
|
NoSQL 关系型数据库 MySQL
MySQL与Redis协同作战:优化百万数据查询的实战经验
【10月更文挑战第13天】 在处理大规模数据集时,传统的关系型数据库如MySQL可能会遇到性能瓶颈。为了提升数据处理的效率,我们可以结合使用MySQL和Redis,利用两者的优势来优化数据查询。本文将分享一次实战经验,探讨如何通过MySQL与Redis的协同工作来优化百万级数据统计。
55 5
|
1月前
|
SQL 资源调度 分布式计算
如何让SQL跑快一点?(优化指南)
这篇文章主要探讨了如何在阿里云MaxCompute(原ODPS)平台上对SQL任务进行优化,特别是针对大数据处理和分析场景下的性能优化。
下一篇
无影云桌面