MySQL浅析之日志模块(1)

MySQL浅析之日志模块

1. 前言

我们都知道在MySQL中有一个重要的模块 – 日志模块。

其实在MySQL中,日志模块分为两种。

  • 一种是记录Server层的日志,称为binlog。
  • 一种是记录引擎层的日志,称为redo log ,这个日志是InnoDB引擎独有的。

2. 什么是redo log

假设我们要对一条数据进行修改,InnoDB是把数据从磁盘读取到内存的缓冲池上进行修改。这时,缓存数据与磁盘中的数据就不一致。我们该怎么做?没错,就是将缓存中的数据写回磁盘即可。但是,我们不难发现,用此方法的话,以后每对缓存中数据进行操作都要立马写回磁盘,这样还产生大量的IO操作,严重影响InnoDB的性能。那么InnoDB是如何处理的呢?我们不妨设想一下一个场景:


假设我们是一个贷款机构,有许多人都要在我们这进行贷款。这时,我们肯定需要一个账本来记录贷款人的记录。如果人少的话,我么当然可以直接在这个账本上进行修改等操作。但是,如果人一旦多了,我们就要每次都要翻这个账本进行修改。那么这个操作太麻烦了。

因此,我们可以单独弄一个小本本 – 用他来记录当天的贷款还款情况:

张三 贷款 5
李四 贷款 12
张三 还款 4
...

类似这样,记录当天情况,等不太忙时,最后进行汇总,记录到账本中。这样就可以大大简化操作。


而InnoDB就是使用这个方法,即WAL技术,WAL的全称是Write-Ahead-Logging,它的关键点就是先写日志,再写磁盘。即类似上一个案例中,先写在小本本中,等不忙的时候再写入账本中。

具体来说,当有一条记录需要更新的时候,InnoDB引擎就会先把记录写到redo log里面,并更新内存,这个时候更新就算完成了。同时,InnoDB引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做。


回到案例中,正当我们用此方法极大简化操作时,突然某一天,贷款人居多,我们的小本本都被记录满了,这时我们该怎么办?

没办法,我们只有放下手中的话。先将小本本的内容更新到账本中,再擦除小本本的记录,腾出空间,用来记录新的记录。


于此类似,**InnoDB的redo log是固定大小的,比如可以配置为一组4个文件,每个文件的大小是1GB,那么这个小本本总共就可以记录4GB的操作。从头开始写,写到末尾就又回到开头循环写,**如下面这个图所示。

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

checkpoint是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。

我们要始终记得,他是一个类似于环形的结构。

因此,write pos和checkpoint之间的是还空着的部分,可以用来记录新的操作。如果write pos追上checkpoint,表示空间满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把checkpoint推进一下。

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


到此redo log 大概原理就是如此,下面让我们看看binlog吧!

3. 什么是binlog

我在前言中提到,binlog是Server层的日志。那么问题来了,有了redo log 为什么还有binlog?

其实是这么一回事:

因为最开始MySQL里并没有InnoDB引擎。MySQL自带的引擎是MyISAM,但是MyISAM没有crash-safe的能力,binlog日志只能用于归档。而InnoDB是另一个公司以插件形式引入MySQL的,既然只依靠binlog是没有crash-safe能力的,所以InnoDB使用另外一套日志系统——也就是redo log来实现crash-safe能力。

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

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

看看区别,那么binlog我们可以用来干什么呢?


让我们注意一下这点:binlog是可以追加写入的。“追加写”是指binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。

没错,binlog的日志是不会被覆盖的。那么就简单了,比如说我们要将数据库恢复到一个月前的具体某一秒状态,该怎么做?

很显然用redo log 是不行的,因为它是会被擦除的。而使用binlog就没有这个问题了。

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

这样你的临时库就跟误删之前的线上库一样了,然后你可以把表数据从临时库取出来,按需要恢复到线上库去。

4. 两阶段提交

当用上这两个日志模块时,其实我们不难发现有一个重大的问题,就是如何保证数据的一致性

这时,有可能不太懂了。别急,让我举个例子:

当我们更改数据时,肯定对这两个日志模块进行写操作,那么先写谁呢,会带来什么后果呢?

让我们分情况来看看:


我们有这么一张表

create table Student(id int primary key, age int);

我们再执行一条语句

update Student set age = age + 1 where id = 1;

将id为1的学生年龄加1。(我们假设age默认为20)

重点来了


先写redo log后写binlog

假设在redo log写完,binlog还没有写完的时候,MySQL进程异常重启。

因为写了redo log 因此恢复数据后, age进行了加1,得到21。但是由于binlog没写完就crash了,这时候binlog里面就没有记录这个语句。因此,之后备份日志的时候,存起来的binlog里面就没有这条语句。

之后问题就显而易见了,如果再某一天需要使用binlog恢复临时数据库的话,由于没有几句age加1这条语句,恢复后的数据中age的值为20,这样明显与我们的原数据是不一致的。

先写binlog后写redo log

如果在binlog写完之后crash,由于redo log还没写,崩溃恢复以后这个事务无效,所以这一行age的值依然是默认值20。

但是binlog里面已经记录了“把age加1”这个日志。所以,在之后用binlog来恢复的时候就多了一个事务出来,恢复出来的这一行age的值就是21,与原库的值不同。


因此我们需要一个解决数据一致的方法,那就是使用两阶段提交

redo log 分为prepare和commit两个阶段,即为两阶段提交

5. 小结

我们了解了MySQL中的日志模块,即物理日志redo log逻辑日志binlog

redo log用于保证crash-safe能力,保证MySQL异常重启之后数据不丢失,以及减少IO操作,占用MySQL性能。

binlog用于数据备份,能够精确还原长时间前的数据。

最后介绍了两阶段提交,通过redo log 的两个阶段,用于保证数据一致性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值