MySQL执行流程

MySQL执行流程

文章目录

  • MySQL执行流程
    • 1. Server层(以查询⭐一条语句的执行流程为例讲解)
      • 1. 连接器
        • 长连接导致的内存OOM如何解决?
      • 2. 查询缓存(MySQL 8.0后没这个功能了)
        • 1. 效率问题
        • 2. 配置查询缓存(是否使用它)
      • 3. 分析器
      • 4. 优化器
      • 5. 执行器
    • 2. 更新一条语句的执行流程(日志模块)
      • 1. redo log(重做日志)(InnoDB引擎特有的日志)
      • 2. binlog(归档日志)
        • 1. 为什么会有两份日志呢?
        • 2. redo log和binlog的区别
      • 3. 更新具体流程
        • 1. 数据库的恢复
        • 2. 两阶段提交(⭐)

MySQL可大致分为两部分:

  • Server层
    包含了连接器、查询缓存、分析器、优化器、执行器,涵盖了MySQL大多数核心服务功能,以及所有的内置函数,所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等;
  • 存储引擎层
    负责数据的存储和提取,其架构模式是插件式的,支持InnoDB、MyISAM、Memory等多个存储引擎,MySQL5.5.5开始默认引擎为InnoDB;

执行一条SQL语句的大致流程为:
MySQL执行流程_第1张图片

注意:不同的存储引擎共用一个Server层

1. Server层(以查询⭐一条语句的执行流程为例讲解)

1. 连接器

所谓连接器,就是我们要连接到MySQL服务器上,即执行:

mysql -uroot -p

接下来会输入密码,如果输入正确,连接器会到权限表里面查出你拥有的权限,之后,这个连接里面的权限判断逻辑,都将依赖于此时读取到的权限;

当一个用户建立连接后,即使你用管理员账号对这个用户的权限做了更改,也不会影响已经连接的客户的权限,修改完成之后,只有再新建的连接才会使用新的权限设置;

在客户端连接之后,如果太长时间没有任何操作,就会自动断开连接,默认8小时(wait_timeout参数控制),数据库分为长连接和短连接

  • 长连接:指连接成功后,如果客户端有持续请求,用的都是一个连接
  • 短连接:每次执行完几次很少的查询就断开连接

长连接导致的内存OOM如何解决?

在有时候使用长连接,会发现MySQL占用内存涨的特别多,这是因为MySQL在执行过程中临时使用的内存是管理在连接对象里的,这些资源在断开连接的时候才会释放,如果长连接累计下来,可能导致内存占用过大,被系统强行杀掉(OOM),代表着MySQL强行重启了;

解决方案:

  • 定期断开长连接
  • 在MySQL5.7之后,你可以在你执行一个比较重的操作后,通过执行mysql_reset_connection来重新初始化连接资源;
    • 这个过程不需要重新连接和权限认证,但是会将连接恢复到刚刚创建的时候的状态

2. 查询缓存(MySQL 8.0后没这个功能了)

建立好连接之后,就是执行select语句了,执行的下一步就是查询缓存了;

MySQL拿到一个查询请求之后,会先到查询缓存看看,之前是不是执行过这条语句,之前如果执行过的语句,就会以key–value的形式放入查询缓存中,如果你的查询(key)能在查询缓存里查找到,就可以直接返回这个value; 如果查询语句不在查询缓存中,再执行后面流程,最后查询到了再将结果存入缓存中;

1. 效率问题

可以看到,能从查询缓存里取结果免去了后面的步骤,效率自然会很高!
但是一般建议不要使用查询缓存,原因有以下几点:

  • 查询缓存失效非常频繁,只要有对一个表的更新操作,这个表上的所有查询缓存都会被清空
  • 对于更新压力大的数据库来说,当你费劲把结果存进查询缓存里,结果一更新就没了,效率会很低下;
  • 对于静态表,如系统配置表,可以使用查询缓存

2. 配置查询缓存(是否使用它)

所以,MySQL把查询缓存的使用权交给了用户,你按照自己的需求去决定是否使用

可以将参数query_cache_type设置成DEMAND,这样对于默认的SQL语句都不适用查询缓存,而对于你确定要使用查询缓存的语句,可以如下指定:

select SQL_CACHE * from user;

注意:MySQL 8.0 以后直接删掉了缓存查询这个功能

3. 分析器

如果没有命中缓存,就来到这一步了,分析器的功能如下:

  • 分析器会先做“词法分析”,即分析你这句SQL语句里面包含了哪些单词
  • 分析出单词后,接下来判断出单词中哪些对应是什么关键字(比如要识别select等等)
  • 接下来是语法分析 ,根据语法规则,判断你的语法是否正确,不正确则会受到错误提醒

4. 优化器

经过了分析器后,MySQl就知道你的语句要做什么了,但在开始执行前,还得经过优化器的处理;

优化器的功能为:

  • 在表里有多个索引的时候,优化器来决定使用哪个索引;
    或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序;
mysql> select * from user join orders using(id) where user.sex='1' and
    -> orders.id < 10;

例如上面的连接查询(注意使用using时,关联两个表的属性名必须相同),有两张方式去执行:

  • 既可以先从表user里面取出sex为1的id值,再根据id值关联到orders,再判断orders里面的值是否等于20;
  • 也可以先从表orders里面取出id小于10的id值,再根据id值关联到user,再判断user里面sex是否为1;

这两种执行方法的逻辑结果是一样的,但是执行的效率会有所不同,而优化器的工作就是去抉择使用哪一种方案;

5. 执行器

经过上面流程,接下来就开始真正的执行MySQL语句了;

  • 第一步先判断是否具有对这个表执行查询的权限,如果没有,就会返回没有权限的错误;
  • 如果拥有权限,就打开这个表,接下来执行器会根据表的引擎定义,去使用这个引擎提供的接口;

例子:

mysql> select * from user where id = 10;
+----+----------+------------+------+---------+
| id | username | birthday   | sex  | address |
+----+----------+------------+------+---------+
| 10 | tom      | 2014-07-10 | 1    | BJ      |
+----+----------+------------+------+---------+
1 row in set (0.01 sec)

在执行器中的流程大致如下:

  • 调用InnoDB引擎接口取这个表的第一行,判断id是否为10,如果不是则跳过,如果是则将这一行数据存放在结果集中
  • 调用引擎接口取下一行,重复相同的判断逻辑,直到这个表的最后一行
  • 返回结果集

2. 更新一条语句的执行流程(日志模块)

上面讲述了查询一条语句的基本流程,那么更新一条语句又是如何执行的呢?

更新和查询的流程其实是差不多的;

例如执行下面的查询语句:

mysql> update user set id=id+1 where id=10;
Query OK, 1 row affected (0.25 sec)
Rows matched: 1  Changed: 1  Warnings: 0

前面也说过,对表的更新会导致这个表的查询缓存全部失效,这就是我们不推荐使用查询缓存的原因!

同样,更新操作同样会走查询操作的那一套流程,只不过更新流程还涉及两个重要的日志模块,它们正是我们今天要讨论的主角:

  • redo log(重做日志)
  • binlog(归档日志)

1. redo log(重做日志)(InnoDB引擎特有的日志)

试想这样一个问题:如果每次更新操作都需要写进磁盘,然后磁盘也要找到对应的那条记录,然后再更新,整个过程IO成本、查找成本都会很高,所以MySQL设计了一种思路来解决这种问题从而提高效率;

具体流程:
当有一条记录需要更新的时候,InnoDB引擎就会先把记录写到redo log中,并更新内存,这个时候更新就算完成了,然后,InnoDB会在适当的时候,将这个操作记录更新到磁盘里面,这个操作往往在系统比较空闲的时候;

InnoDB的redo log是固定大小的,这就引发了一个问题,当redo log记录满的时候,该怎么办?
这时当记录满的时候,就会将一部分更新到磁盘里,就又有新的空间了,过程如下图:

MySQL执行流程_第2张图片

说明:

  • 顺时针看,check point到write pos之间的区域都写满了,当write pos 达到check point时,就不能再执行新的更新,得停下来把check point处擦除,把check point点顺时针推进一些,擦除记录前要把记录更新到数据文件
  • 有了redo log,InnoDB就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称之为crash-safe ;(⭐)

2. binlog(归档日志)

前面我们讲了,MySQL整体架构来看,分为Server层和引擎层,上面的redo log是InnoDB引擎特有的日志,而Server层也有自己特有的日志,就是binlog——归档日志;

1. 为什么会有两份日志呢?

因为最开始MySQL里并没有InnoDB引擎。MySQL自带的引擎是MyISAM,但是MyISAM没有crash-safe的能力binlog日志只能用于归档 ,所以得使用InnoDB的redo log,来保证实现crash-safe能力!

2. redo log和binlog的区别

  • redo log是InnoDB特有的,而binlog是MySQL的Service层实现的,所有的引擎都可以使用;
  • redo log是物理日志,记录的是“在某个数据页上做了什么修改”,而**binlog是逻辑日志,记录的是这个语句的原始逻辑,**比如“给id=10这行的name属性变成“yy””;
  • redo log是循环写的,空间是固定的,总会用完,而binlog是可以追加写入的,所谓的追加写是指binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志;

3. 更新具体流程

MySQL执行流程_第3张图片

具体的更新流程就如上图,观察后面,你可以发现将redo log的写入拆成了两步,这是为什么呢?

这就是“两阶段提交”,在此之前,我们先来看一下数据库的备份与恢复;

1. 数据库的恢复

相信大家都听过这样一句话:数据库能恢复到半个月以内的任意一秒的状态;

这是怎么实现的呢?
肯定是得依靠binlog呗!!前面才说到,binlog存放的是所有的逻辑操作,而且采用追加写的形式,如果你的DBA承诺半个月可以恢复,那么备份系统中一定会保存最近半个月的所有binlog,同时系统会定期做整库备份 ,这里的定期具体是多长时间,取决于系统的重要性,可以是一天一备,也可以是一周一备;

好了,那么假如我在9月5日的某一秒状态误删了一个表,我该怎么恢复呢?

  • (1)找到这天之前最近的一次整库备份;
  • (2)从备份的时间点开始,将备份的binlog依次取出来重放到误删表之前的那个时刻;

2. 两阶段提交(⭐)

为什么一定要两阶段提交呢?

binlog和redo log是两个独立的逻辑,两阶段提交目的就是让这两个状态保持逻辑上一致
如果不用两阶段提交,只会有下面两种情况: (反证法)
(以上面的更新例子来说)

  • (1)先写redo log再写binlog

    • 假如在redo log写完后,binlog还没有写完的时候MySQL异常重启(crash),binlog中就没有记录更新的这条语句,我们说过,redo log拥有crash-safe的能力,所以在重启后,数据库的内容是更新后的内容,假如在此之前备份了数据库,之后要通过这来恢复数据库,我们知道,恢复数据库是依靠备份+binlog来完成的,此时binlog又不完整,所以在以后恢复数据库的内容是不正确的;
  • (2)先写binlog再写redo log

    • 假如在写完binlog之后,redo log还没写完就crash了(MySQL异常重启),我们知道binlog是没有crash-safe能力的,所以数据库崩溃重启之后这个事物是无效的,也就是这个更新是未完成的,但我们的binlog却写入了这条记录,当以后需要恢复数据库的时候会用到binlog,自然会多了一条更新信息,导致恢复的数据库数据不正确;

你可能感兴趣的:(MySQL)