MySQL学习-1:mysql中一个select语句的执行过程分析

假设我们现在有个最简单的表,表里只有一个 ID 字段,在执行下面这个查询语句时:

mysql> select * from T where ID=10;

他的内部执行过程是什么呢?

一、MySQL的大体结构

首先我们来将MySQL拆解一下,看看里面都有哪些“零件”。下面是 MySQL 的基本架构示意图,从中你可以清楚地看到 SQL 语句在 MySQL 的各个功能模块中的执行过程

大体来说,MySQL 可以分为 Server 层和存储引擎层两部分。

server层就是我们上面看到的连接器,分析器等。他们是负责实现了MySQL的核心功能的一部分内容。

存储引擎层是负责数据的存储和提取,其中存储引擎的架构模式是插件式的,支持InnoDb、MyISAM、Memory等多个存储引擎,目前最常用的是InNoDB。从MySQL5.5.5开始成为默认的存储引擎。

二、每个组件的功能探究

连接器

想要成功的执行一条SQL,第一步需要我们先连接上MySQL数据库。连接器的作用就是:与客户端建立连接,获取权限,维持和管理连接等。连接命令一般是这么写的:

mysql -h$ip -P$port -u$user -p

不直接在这条连接命令上输入数据库密码的原因是因为,这样容易遭成密码泄露。

如果输入的用户名和密码不对,就会收到一个错误提示:"Access denied for user"。如果用户名密码验证通过,连接器会去权限表中查询当前登录用户所拥有的权限都有哪些。之后,基于这个连接的所有权限判断逻辑,都将依赖于此次读到的权限为准。也就是说,假如一个用户建立连接之后,你再去通过管理员修改这个用户的权限,将会在下一次连接中生效。即如果想要修改立即生效,需要断开重连。

连接成功之后,没有进行后续操作的话,这个连接的状态就是空闲(sleep),可以通过show processlist命令来查看。

如果说一个链接处于空闲状态超过一定时间,连接器就会自动断开,这个时间是由参数 wait_timeout 控制的,默认值是 8 小时。因此在我们的程序中,如果用到了连接池的时候,连接池会维护一些空闲连接以保持性能,我们在使用这些连接进行操作之前,就需要事先检查一下,这个连接是否已经被MySQL服务器断开了。不同的连接池都会有关于这个的配置。

另外,在数据库连接中有长连接和短连接的区分

长连接:是指连接成功后,如果客户端持续有请求,则一直使用同一个连接。

短连接:则是指每次执行完很少的几次查询就断开连接,下次查询再重新建立一个。

因为建立连接的过程通常是比较复杂的,所以建议大家在使用中要尽量减少建立连接的动作,也就是尽量使用长连接。

连接全部使用长连接后,你可能会发现,有些时候 MySQL 占用内存涨得特别快,这是因为 MySQL 在执行过程中临时使用的内存是管理在连接对象里面的。这些资源会在连接断开的时候才释放。所以如果长连接累积下来,可能导致内存占用太大,被系统强行杀掉(OOM),从现象看就是 MySQL 异常重启了。

解决MySQL长连接内存占用问题,可以有两个解决方案:

1、定期断开长连接。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后要查询再重连。

2、如果你用的是 MySQL 5.7 或更新版本,可以在每次执行一个比较大的操作后,通过执行 mysql_reset_connection 来重新初始化连接资源。这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态。

查询缓存

建立好连接之后,执行逻辑会首选去 查询缓存 里查一下,是否有执行过这条SQL语句。之前执行过的语句及其结果可能会以 key-value 对的形式,被直接缓存在内存中。key 是查询的语句,value 是查询的结果。如果你的查询能够直接在这个缓存中找到 key,那么这个 value 就会被直接返回给客户端。

如果缓存中没有,就会来到分析器、优化器、执行器这一条路线上来。

但是建议大家,不要使用查询缓存。查询缓存往往利大于弊。

只要我们更新了一个表,那么这个表的查询缓存就会失效,然后被清空。因为在实际的业务场景中,可能我们存储的缓存还没有被使用就会失效被清除掉。因此对于更新压力大的数据库来说,查询缓存的命中率会非常低。除非你的业务就是有一张静态表,很长时间才会更新一次。比如,一个系统配置表,那这张表上的查询才适合使用查询缓存。

MySQL 为我们提供了“按需使用”的方式。可以将参数 query_cache_type 设置成 DEMAND,这样对于默认的 SQL 语句都不使用查询缓存。而对于你确定要使用查询缓存的语句,可以用 SQL_CACHE 显式指定,像下面这个语句一样:

mysql> select SQL_CACHE * from T where ID=10;

需要注意的是,MySQL 8.0 版本直接将查询缓存的整块功能删掉了,也就是说 8.0 开始彻底没有这个功能了。

分析器

没有命中缓存,就会真正的去执行这条SQL语句,之前之前首先就来到分析器这里。分析器会对你要执行的这条SQL语句进行一次解析,具体的解析内容包括:

词法分析:MySQL从你输入的"select"这个关键字识别出来,这是一个查询语句。它也要把字符串“T”识别成“表名 T”,把字符串“ID”识别成“列 ID”。

语法分析:根据词法分析的结果,根据语法规则再去判断这条SQL语句是否符合语法。比如select关键字拼错,比如缺少分号等等。这个时候就会返回给我们一个我们非常熟悉的语法错误"You have an error in your SQL syntax".一般语法错误会提示第一个出现错误的位置,所以你要关注的是紧接“use near”的内容。

优化器

通过分析器的分析,MySQL就知道你要做什么操作了。但是在开始执行之前,还是要先经过优化器的处理。

优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序。比如你执行下面这样的语句,这个语句是执行两个表的 join:

mysql> select * from t1 join t2 using(ID)  where t1.c=10 and t2.d=20;
  • 既可以先从表 t1 里面取出 c=10 的记录的 ID 值,再根据 ID 值关联到表 t2,再判断 t2 里面 d 的值是否等于 20。
  • 也可以先从表 t2 里面取出 d=20 的记录的 ID 值,再根据 ID 值关联到 t1,再判断 t1 里面 c 的值是否等于 10。

这两种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择使用哪一个方案。

执行器

在开始执行之前,会判断一下,当前用户对于要查询的表是都有查询权限,如果没有的话,就会返回没有权限的错误,如下

mysql> select * from T where ID=10;

ERROR 1142 (42000): SELECT command denied to user 'b'@'localhost' for table 'T'

如果是命中了缓存的话,会在返回缓存结果之前,做权限的验证。如果没有权限也会返回同样的错误。

但是这里我们思考一个问题,为什么,不管是命中缓存还是说执行查询语句,都是等到要最后一步之前去权限验证,而不是说最开始,连接上数据库之后,就去验证这个权限呢?

后来想了一下,因为MySQL的权限的颗粒度是精确到每一个操作的,所以说会存在一个用户拥有select权限,但是没有update和insert权限,所以想要知道这条SQL语句到底能不能被执行,最起码得是进过分析器之后,知道了是insert、update、select、delete中的哪一个的时候,才能去判断,是都有权限执行。

另外在优化器之前也会调用precheck 验证权限。

如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。

比如我们这个例子中的表 T 中,ID 字段没有索引,那么执行器的执行流程是这样的:

1、调用 InnoDB 引擎接口取这个表的第一行,判断 ID 值是不是 10,如果不是则跳过,如果是则将这行存在结果集中;

2、调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。

3、执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。

至此,这个语句就执行完成了。

对于有索引的表,执行的逻辑也差不多。第一次调用的是“取满足条件的第一行”这个接口,之后循环取“满足条件的下一行”这个接口,这些接口都是引擎中已经定义好的。

有关索引部分的内容,我们应该在另一部分做一个单独的研究。

延伸思考:

如果表 T 中没有字段 k,而你执行了这个语句 select * from T where k=1, 那肯定是会报“不存在这个列”的错误: “Unknown column ‘k’ in ‘where clause’”。你觉得这个错误是在我们上面提到的哪个阶段报出来的呢?

本文为极客时间课程:MySQL实战45讲学习笔记

猜你喜欢

转载自blog.csdn.net/qq_39915083/article/details/107744156