01 基础架构:一条SQL语句是如何被执行的?

  很多时候,我们对于数据库的使用,仅仅停留在输入一条mysql语句,返回一个结果集,却不知道这条语句在MYSQL内部是怎么实现的,所以本篇将介绍MYSQL最基本的架构组成,通过对MYSQL的拆解,使得我们对MYSQL能有一个更好的认识。

  下图是MYSQL的基本架构示意图:

img

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

  Server层包括连接器,查询缓存,分析器,优化器,执行器等,涵盖了MySQl大部分核心服务功能,以及所有的内置函数(如日期,时间,数学和加密函数)。

  而存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持InnoDB,MyIsAM,Memory等多个存储引擎,现在最常用的存储引擎是InnoDB,它从MySQL5.5.5开始成为了默认的存储引擎。

  不同的存储引擎的表数据存取方式不同,支持的功能也不同。如果不想使用默认的InnoDB存储引擎,也可以在创建表的时候指定别的存储引擎,如在create table 建表的时候,可以使用 engine = memory 来指定使用内存引擎来建表。

  从上面图中可以看出,不同的存储引擎共用一个Server层。

1. 连接器

  连接数据库的工作就是连接器来完成的。连接器负责跟客户端 建立连接,获取权限,维持和管理连接

  连接命令是这么写的:mysql -h(ip) -P(port) -u(user) -p(password)

  连接命令中的mysql是客户端工具,用来跟服务器建立连接。在经历经典的TCP握手后,连接器就会验证输入的用户名和密码:

  • 如果用户名或密码错误,就会收到一个“Access denied for user ” 的错误,然后客户端程序执行结束。
  • 如果验证通过,则连接器会读取权限表中你的所有权限,此后这个连接的所有权限判断逻辑,都将依赖此时读到的权限。

所以,当用户成功建立连接后,即使管理员修改该用户的权限,依然不会影响到此时连接的权限。修改完成后,只有建立新的连接才会使用新的权限设置。

 
  连接成功后,如果没有进行后续的动作,这个连接状态就处于空闲状态。我们可以通过 show processlist; 这个命令来查看
image-20210517221711339
  图中 Command 列显示为 Sleep 的这一行,就代表着系统中有一个空闲连接。
 

  那么,如果该连接一直处于空闲状态,系统会做出什么操作呢?

  答案是:客户端如果长时间没有动静,系统会自动将它断开,这个断开的时间是由 参数 wait-timeout 控制的,默认是8个小时。如果在断开之后,客户端发送请求,则会受到 “ Lost Connection to MySQL server during query”的错误提醒,此时如果想要继续请求,只能进行重连。
 

  数据库中有两个概念:长连接 和 短连接。

  • 长连接:指连接成功后,如果客户端持续有请求,则使用同一个连接。
  • 短链接:指每次执行完很少的查询后就断开连接,下次查询再重新建立连接。

  因为建立连接是一个很复杂的过程,所以尽量减少建立连接的动作,也就是多用长连接。

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

针对这种情况,可以考虑以下两种方案:

  • 定期断开连接。在使用一段时间,或者在程序中判断执行过一个占用内存大的查询后,就断开连接,下次需要查询时再重新建立连接。
  • 如果用的是MySQL5.7或者更新的版本,可以在执行完一次大的操作后,执行 mysql_reset_connection 来重新初始化连接资源。这个过程不需要重连和重新进行权限验证,但是可以将连接恢复到刚创建时的状态。

2.查询缓存

  建立连接后,就可以执行select查询语句了。执行逻辑就会来到了第二步,查询缓存。

  MySQL拿到一个请求后,会先到查询缓存中查看是否之前执行过这条语句。之前执行过的语句和返回的结果可能会以 key-value 的形式被直接缓存在内存中。key 是指查询语句,而value 是指结果集。

  • 如果查询的语句能够直接在缓存中找到key,则对应的 value 会直接被发送给客户端。
  • 如果语句不在查询缓存中,则会继续后面的执行阶段。

  这样看来,如果命中查询缓存,那么就不用执行后面复杂的阶段,那么,我们就要多多使用查询缓存吗?
  答案是:并不是,查询缓存往往弊大于利。因为查询缓存的失效很频繁,只要对于一个表的更新,这个表上的查询缓存都会被清空,因此可能你费尽地把结果存起来,还没使用,就被一个更新清空了。对于更新压力比较大的数据库来说,命中缓存的概率很低,而如果业务中有一张静态表,很长时间才更新一次,例如一张系统配置表,那么这张表上的查询才适合用查询缓存。

  不过MySQL提供了一种按需使用的方式。如果对于所有的查询都不想使用查询缓存,可以将参数 query_cache_type 设置成 DEMAND(按需及用)方式。而对于确定要使用查询缓存的语句,也可以用 SQL_CACHE 显式指定,如下:

select SQL_CACHE * from table where id = 10;

查看查询缓存情况命令:show variables like '%query_cache%';

  不过需要注意的是:MySQL8.0 版本已经直接将查询缓存这块功能删除了,也就是8.0开始彻底没有这个功能了。

3.分析器

  如果查询语句没有命中查询缓存,那么就会真正地开始执行SQL语句了。首先MySQL需要知道你这条语句是要干什么,所以会先对语句进行解析。

  每一条语句都是由一个个字符串和空格组成,MySQL需要知道这些字符串分别代表什么意思,所以分析器会先进行词法分析。例如分析器从语句中的“select”关键字识别出这是一条查询语句,它也要将table识别成表名,将id识别成字段等。

  接下来就是进行语法分析。分析器会根据语法规则对这条语句进行分析,判断是否满足MySQL语法。如果不满足,则会收到You have an error in your SQL syntax 的错误信息。

4.优化器

经过分析器的分析后,并不是直接进入执行器阶段,而是先经过优化器的处理。

优化器在对于表中有多个索引时,决定使用哪个索引;或者在多表关联时,决定各个表的执行顺序。例如执行下面的语句:

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。

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

优化器阶段完成后,这条语句的执行方案也就确定下来了,接下来就是进入执行器阶段。

5.执行器

  MySQL通过了分析器知道了这条语句要做什么,通过优化器确定了这条语句该怎么做,就可以进入执行器阶段,执行这条语句。

select * from table where id = 10; 为例

执行器的执行过程如下:

  1. 先判断有没有对这张表的查询权限。如果没有,则会返回没有权限的错误;如果有,则打开表继续执行。打开表时,执行器会根据这张表的引擎定义,去使用该存储引擎提供的接口。
  2. 接下来会调用InnoDB存储引擎接口取表中的第一行数据,判断id值是否为10;如果是,则将这行数据存在结果集中,如果不是,则跳过。
  3. 继续调用引擎取下一行,重复上面的操作,直到表的最后一行数据。
  4. 将所有满足条件的数据作为结果集返回给用户。

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

  我们可以在数据库的慢查询日志中看到一个 rows_examined 字段,表示这个语句在执行过程中扫描了多少行,这个值就是每次执行器调用引擎获取数据行累加得到的。

  在有些场景下,执行器调用了一次,而存储引擎扫描了多行,因此 引擎扫描的行数 和 rows_examined 是并不完全相同的。

相关问题

  1. 为什么对权限的检查不在执行器阶段之前做?

    答:有些时候,SQL语句要操作的表不只是SQL字面上那些。比如如果有个触发器,得在执行器阶段(过程中)才能确定。优化器阶段前是无能为力的

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

    答:分析器阶段。

猜你喜欢

转载自blog.csdn.net/OYMNCHR/article/details/116999965