MySQL45讲-2-一条SQL更新语句是如何执行的?

前面我们了解了SQL查询语句是如何执行的,一条SQL查询语句的过程需要经过连接器、分析器、优化器、执行器等功能模块,最终到达存储引擎。

在MySQL中,可以恢复到半个月内的任何一个时间点,这时基于日志系统来实现的。

更新语句的流程

在这个例子中,假设创建了表T。

create table T(ID int primary key, c int)

MySQL45讲-2-一条SQL更新语句是如何执行的?

上面显示了前面提到的查询语句的流程,首先要确定的是更新语句的过程与查询语句的过程相同。

[En]

The flow of the query statement mentioned earlier is shown above, and the first thing to be sure is that the process of updating the statement is the same as that of the query statement.

再假如我们在表T中执行更新语句如下:

update T set c = c + 1 where ID = 2;

执行任何语句前都需要连接上MySQL,这就是 连接器的工作。

接下来,因为我们执行的是更新操作,所以 查询缓存就需要刷新以确保缓存中数据的正确性, 因此需要将表T中的查询缓存都清空,这也是之前不推荐使用查询缓存的原因。

接下来, 分析器会通过词法和语法解析知道这是一条更新语句。

优化器决定来使用ID这个索引。

然后, 执行器负责具体执行,找到对应的数据行,然后执行更新。

与查询流程不一样的是,更新流程还涉及两个重要的日志模块(redo\ log)(重做日志)和(bin\ log)(归档日志)。

redo log

在MySQL中,如果每一次的更新都需要重新写入磁盘,然后从磁盘中找到那条记录,然后再执行更新,整个过程的I/O成本、查找成本都很高,为了解决这个问题,MySQL使用了WAL技术,全称为(Write-Ahead-Logging),关键点在于 先写日志,再写磁盘。

具体的做法是,当有记录需要更新时,InnoDB引擎会先把记录写到(redo\ log)中,这时更新就算是完成了。同时,InnoDB引擎会在适当的时候,将这次操作更新到磁盘中,这个更新往往是在系统比较空闲的时候执行的。

然后继续关联,如果更新的记录不多,系统可以在空闲时等待真正的更新。如果有更多更新的记录,它必须暂时停止系统,将更新写入磁盘,然后从日志中删除这些记录,以便为新的更新提供空间。

[En]

Then continue to associate, if there are not many updated records, the system can wait for the real update when it is idle. If there are more updated records, it has to stop the system temporarily, write the updates to disk, and then remove these records from the log to provide space for new updates.

InnoDB的(redo\ log)大小是固定的,比如可以配置一组4个文件,每个文件的大小是1GB,那么整个日志的大小就是4GB。

MySQL45讲-2-一条SQL更新语句是如何执行的?

如上所示,从头写到尾,从头写到尾。

[En]

As shown above, write from the beginning to the end and from the beginning to the end.

(write\ pos)是当前记录的位置,一边写一边后移,写到第3号文件末尾就又从第0号文件开头开始写。

(checkpoint)是当前要擦除的位置,也是往后推移并且循环的。

(write\ pos)和(checkpoint)之间的空间是空着的部分,用来记录新的操作。

当(write\ pos)追上(checkpoint)后,表示(redo\ log)满了,这时候不能执行新的更新,必须先把操作写入磁盘后,才能继续更新,需要把(checkpoint)往后推移一些距离。

有了(redo\ log),InnoDB就可以保证即使数据库发生了异常重启,之前提交的记录不会丢失,这个能力成为(crash-safe)。

bin log

前面的(redo\ log)更多的是数据引擎层面的日志,在Server层也有(bin\ log)日志。

因为最初时MySQL并没有InnoDB引擎,自带的引擎是MyISAM,MyISAM并不具备(crash-safe)能力,(bin\ log)日志用于归档。

这两种原木有三点不同。

[En]

There are three differences between the two kinds of logs.

  1. (redo\ log) 是 InnoDB 引擎特有的;(bin\ log) 是 MySQL 的 Server 层实现的,所有引擎都可以使用。
  2. (redo\ log) 是物理日志,记录的是”在某个数据页上做了什么修改”;(bin\ log) 是逻辑日志,记录的是这个语句的原始逻辑,比如”给 ID=2 这一行的 c 字段加 1 “。
  3. (redo\ log) 是循环写的,空间固定会用完;(bin\ log) 是可以追加写入的。”追加写”是指(bin\ log) 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。

案例分析

在上面提到的例子中,update语句的流程如下:

  1. 执行器先找引擎取ID=2这一行。ID是主键,引擎直接到主键索引树上搜索找到这一行。如果ID=2这一行所在的数据页本来就在内存中,就直接返回给执行器,否则,需要先从磁盘中读入内存,然后再返回。
  2. 执行器拿到引擎给的行数据,把这个值加上1,比如原来是N,现在将其修改为N+1,得到新的一行数据,再调用引擎接口写入这行新数据;
  3. 引擎把这行新数据更新到内存中,同时将这个更新操作记录到(redo\ log)中,此时(redo\ log)处于prepare状态。然后告知执行器执行完成,可以提交事务;
  4. 执行器生产这个操作的(bin\ log),并把(bin\ log)写入磁盘;
  5. 执行器调用引擎的提交事务接口,引擎把刚刚写入的(redo\ log)改成commit状态,表示事务已提交,更新完成。

MySQL45讲-2-一条SQL更新语句是如何执行的?

上图的黑色部分表明它是在执行器中执行的,而亮部分表明它是在发动机中执行的。

[En]

The dark part of the image above indicates that it is executed in the actuator, and the light part indicates that it is executed in the engine.

将(redo\ log)的写入拆分为两个步骤:prepared和commit也成为两阶段提交。

两阶段提交

两阶段提交这是为了使两个日志之间的逻辑一致。

[En]

Two-phase commit this is to make the logic between the two logs consistent.

让我们首先来看看如何使用两个日志进行数据恢复。

[En]

Let’s first take a look at how to use two logs for data recovery.

(bin\ log)会记录下所有的逻辑操作,并且是追加写方式,因此当我们对某个时间的系统进行备份后,如果在某天下午2点发现12点有一次误删操作,需要找回数据,我们可以这么操作。

  • 首先,找到最近的完整备份,比如昨晚的备份,我们会将其备份到临时库
    [En]

    first of all, find the most recent full backup, such as the one last night, and we will back it up to the temporary library*

  • 然后从备份的时间点开始,将备份的(bin\ log)依次取出来,重放到中午误删表之前的那个时刻;

此时,我们的临时图书馆与误删除前的网上图书馆一样。此时,我们将表数据从临时库中取出,并根据需要将其恢复到在线库中。

[En]

At this time, our temporary library is the same as the online library before it was deleted by mistake. At this time, we take the table data out of the temporary library and restore it to the online library as needed.

由于 (redo\ log) 和 (bin\ log) 是两个独立的逻辑,如果不用两阶段提交,要么就是先写完 (redo\ log) 再写 (bin\ log),或者采用反过来的顺序。我们看看这两种方式会有什么问题。

假定当前ID=2的行,字段c的值是0,再假定update过程中,我们写完第一个日志后,第二个日志还没有写入就发生了(crash),会出现如下情况:

  1. 先写(redo\ log) 后写(bin\ log)。假设在(redo\ log) 写完,(bin\ log) 还没有写完的时候,MySQL 进程异常重启。由于我们前面说过的,(redo\ log) 写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行 c 的值是 1。
    但是由于(bin\ log) 没写完就 crash 了,这时候(bin\ log) 里面就没有记录这个语句。因此,之后备份日志的时候,存起来的(bin\ log) 里面就没有这条语句。
    然后你会发现,如果需要用这个(bin\ log) 来恢复临时库的话,由于这个语句的(bin\ log) 丢失,这个临时库就会少了这一次更新,恢复出来的这一行 c 的值就是 0,与原库的值不同。
  2. 先写(bin\ log) 后写(redo\ log)。如果在(bin\ log) 写完之后 crash,由于(redo\ log) 还没写,崩溃恢复以后这个事务无效,所以这一行 c 的值是 0。但是(bin\ log) 里面已经记录了”把 c 从 0 改成 1″这个日志。所以,在之后用(bin\ log) 来恢复的时候就多了一个事务出来,恢复出来的这一行 c 的值就是 1,与原库的值不同。

因此,您可以看到,如果不使用两阶段提交,数据库的状态可能与其日志恢复的状态不同。

[En]

Therefore, you can see that if two-phase commit is not used, the state of the database is likely to be different from that recovered by its log.

事实上,我们不仅需要在恢复过程中迁移数据,当我们需要增加库或备份库的容量来增加系统的阅读能力时,也需要迁移数据。

[En]

In fact, we not only need to migrate data during recovery, but also need to migrate data when we need to increase the capacity of the library or the backup library to increase the reading capacity of the system.

现在的常用做法也是全量备份+(bin\ log)来实现的。

简单说,(redo\ log) 和 (bin\ log) 都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。

Original: https://www.cnblogs.com/nullpointer-c/p/15864479.html
Author: NullPointer_C
Title: MySQL45讲-2-一条SQL更新语句是如何执行的?

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/507840/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 5、枚举Enum

    枚举类会 隐式的继承Enum类,无法再继承其它类( 单继承机制) 一、无实参枚举类型: 1、定义: /** * 1、无实参枚举类型 */ public enum NoParamTy…

    数据库 2023年6月6日
    0104
  • Linux安装 MySQL

    一、下载yum仓库 1、说明:mysql官方提供所有版本的仓库,要使用yum方式安装的话需要提前下载该仓库列表 2、官方下载链接:https://dev.mysql.com/dow…

    数据库 2023年6月9日
    085
  • docker配置容器日志大小限制

    修改docker配置文件/etc/docker/daemon.json,添加如下内容: { "log-driver": "json-file&quot…

    数据库 2023年6月9日
    0104
  • Springboot打包部署项目

    这里打包的是jar项目,也就是没有webapp目录,通过maven打包插件打包发布到服务器 废话不多少直接开撸废话不多少直接开撸废话不多少直接开撸废话不多少直接开撸废话不多少直接开…

    数据库 2023年6月6日
    087
  • String 对象

    String对象及底层区别 String 对象 两种方式 方式一:直接使用双引号得到字符串对象 代码: //方式一:直接使用双引号得到字符串对象 String name = &qu…

    数据库 2023年6月16日
    083
  • MySQL之连接查询和子查询

    多表连接的基本语法 多表连接,即将多个表拼接成一个表,然后进行查询 [En] Multi-table join, that is, several tables are splic…

    数据库 2023年5月24日
    0143
  • 设计模式之七大原则

    1.单一职责原则: 不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责 使用一个列子来表达,一个动物类,动物可以使用里面的方法进行奔跑: //单一职责原则测试 pu…

    数据库 2023年6月6日
    0108
  • springboot~Cache注解缓存在代码中的获取

    对于springboot中基于方法的缓存Caching来说,我们直接以声明的方式添加,删除这些缓存,而它们在redis这种持久化产品中,通过 value::key的方法组成一个re…

    数据库 2023年6月6日
    098
  • centos8安装mysql

    前言 最近在centos8系统下部署django项目时,要用到mysql数据库,在安装中遇到了点坑,之后参考了一位博主的文章,也是顺利的安装配置成功,博主原文连接: ((20条消息…

    数据库 2023年5月24日
    086
  • mysql数据类型与表操作

    一、mysql基本认知 创建用户 create host aa identified with mysql_native_password by ”; 修改用户权限 a…

    数据库 2023年5月24日
    093
  • MySQL MHA 运行状态监控

    一 项目描述 1.1 背景 MHA(Master HA)是一款开源的 MySQL 的高可用程序,它为 MySQL 主从复制架构提供了 automating master failo…

    数据库 2023年6月16日
    0114
  • Jenkins+gitlab+docker+harbor容器化自动部署详细流程

    环境:Linux版本:Centos7 一、更新源:yum update 二、安装docker:yum install docker -y 启动docker: systemctl s…

    数据库 2023年6月16日
    0129
  • 使用mybatis-plus转换枚举值

    1. 使用mybatis-plus转换枚举值 枚举值转换方式有很多,有以下方式: 后端写一个通用方法,只要前端传枚举类型,后端返回相应的枚举值前端去匹配 优点:能够实时保持数据一致…

    数据库 2023年6月11日
    0161
  • 设计模式之(1)——简单工厂模式

    创建型模式:主要用于对象的创建; 结构型模式:用于处理类或者对象的组合; 行为型模式:用于描述类或对象怎样交互和分配职责; ————————————————————————————…

    数据库 2023年6月14日
    098
  • 21浙比武

    可以将获得的windows镜像先挂载获取SAM和SYSTEM注册表文件,然后使用mimikatz 提取windows的密码ntml哈希值 <span class=”ne-te…

    数据库 2023年6月11日
    079
  • 推荐系统!基于tensorflow搭建混合神经网络精准推荐! ⛵

    💡 作者:韩信子@ShowMeAI📘 深度学习实战系列:https://www.showmeai.tech/tutorials/42📘 TensorFlow 实战系列:https:…

    数据库 2023年6月14日
    093
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球