日志系统: 一条SQL更新语句是如何执行的?

前面的文章中,我们系统了解了一个查询语句的执行流程,当连接器连接完成之前先会查询缓存,如果有就会快很多,但是频繁的update操作又会让查询缓存很难受,如果查询不到就会进入分析环节和优化环节,最后是执行环节,这样一个SQL语句就执行完了,这次我们来说一下更新语句的执行过程。

MySQL可以恢复到半个月内任何一秒的状态,这是如何做到的呢?

首先还是从一个普通的update语句开始:

创建一张表,插入数据

mysql> create table T(ID int primary key, c int);

然后更新数据,使用update:

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

更新的流程,也会按照查询的流程走一遍:
在这里插入图片描述
执行命令之前,肯定要先连接数据库,这是连接器的工作。

之前我们说过,update表的时候,查询缓存会失效,然后分析器会通过词法分析,找到update关键字,知道这是一个更新语句。优化器会选择ID作为索引,然后执行器进行具体执行,找到这一行进行更新。

和查询流程不一样的是,update还涉及到了两个重要的日志模块:redo log(重做日志),binlog(归档日志)。

重要的日志模块: redo log

用白话来说 ,当我们的数据量相当大的时候,找到要修改的某一行或者某几行,是很浪费性能的,所以MySQL就通过此日志模块,将要修改的东西进行临时记录,等到适当的时候再进行更新。

用文学点的话来说,MySQL的每一次更新操作都会写进磁盘,然后找到记录,更新。整个过程的IO成本和查询成本都很高,所以就借助了MySQL的WAL技术,WAL的全称是Write-Ahead Logging,它的关键点在于先写日志,再写磁盘。具体来说,当检索一条记录需要更新的时候,就会用InnoDB引擎先把记录写到redo log,然后更新内存,然后在适当的时候,更新硬盘。

redo Log的大小是固定的,当写满后,会从开头覆盖读写。有了redo Log,即使数据库崩溃,也可以将提交的记录保存,这种能力称之为crash-safe。

重要的日志模块: binlog

刚才我们讲了很多关于日志模块,redo Log的事情,我们知道,MySQL的整体,分为两大部分,一部分是Server层,一部分是InnoDB层,刚才所讲的就是InnoDB层引擎独有的日志,而Server层也有自己的日志,称为binLog(归档日志)

那么,MySQL拥有这两种日志的意义何在呢?

因为在早期的MySQL中并没有InnoDB引擎,Mysql自带的引擎是MylSAM,但是MylSAM没有crash-safe的能力,binLog日志只能用于归档。而InnoDB是另一个公司以插件形式引入Mysql的,因为binLog没有crash-safe功能,所以引入redo Log就正好实现了。

这两种日志有以下三点不同。

  • redo log是InnoDB引擎特有的; binlog是MySQL的Server层实现的, 所有引擎都可以使用
  • redo Log是物理日志,记录的是在某个数据页上做了什么修改;bin Log是逻辑日志,记录的是语句的原始逻辑,也就是所谓的白话逻辑。
  • redo Log写完第一轮后会从头覆盖着写第二轮,空间大小固定;bin Log在固定单体文件大小的情况下,会追加去写。

理解了以上两种日志之后,接下来我们来看看,执行器和InnoDB在update语句内部的流程:

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

下面是执行流程图:
在这里插入图片描述
最后一个阶段,写入 redo Log和写入bin Log,被称为两阶段提交。

两阶段相交
目的是为了让两份日志之间逻辑一致。这时候就可以回答之前的问题了。

为什么可以让数据库恢复半个月之前任何一秒的状态

binLog记录所有的逻辑操作。换句话说,如果DBA在可承受范围内,那么就可以找到这个范围所有时间内的bin Log。

如果真的发生了误删的操作,就这样:

  • 首先,找到最近的一次全量备份,如果你运气好,可能就是昨天晚上的一个备份,从这个备份恢复到临时库。
  • 然后,从备份的时间点开始,将备份的binlog依次取出来,重放到中午误删表之前的那个时刻。

这样就可以将表数据从临时库提出恢复到线上。

那么,为什么需要两阶段提交呢

提交顺序很重要吗?我们可以假设这样的情况:

仍然用前面简单的update举例子:

  • 假设我们初始值定的c = 5 ID = 2,这时候写入了日志,但是更新之后,未写入日志之前,发生了crash,会怎么样呢?

    1、先写redoLog 在写binLog。

假设,binLog还没写完就crash了,mysql异常重启,但是我们已经写完了redoLog,只是还没写入磁盘,正在内存里了,所以不影响。但是binLog确实收到了损害,影响就是,我们没有备份这个逻辑语句,所以如果再回头找这条update语句,那就找不到了,那就肯定不行了呢。

2、先写binLog,再写redoLog

假如写完binLog,然后写redoLog,那么就会update失败,但是逻辑存入了临时的模块日志,也就是binLog,当我们重启服务之后,用binLog恢复,就会多出一个事务,所以就会和原来的表中值不一样。所以其实这样是不好的。

但我们不光在这种情况需要binLog进行恢复,如果我们要搭建一些备库来增加系统的读能力,也是需要binLog来实现,那么就会出现第二种情况,主从数据库不一致。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值