MySQL是如何做到异常重启还能保住数据的完整性

前言

redolog

redolog即重做日志,数据库安装目录下的data/ib_logfile指的就是redolog文件。redolog作为一种对数据的恢复操作,恢复的是提交事务修改的页操作。
如当我们执行下面的语句:

start transaction;
select balance from bank where name="zhangsan";
// 生成 重做日志 balance=600
update bank set balance = balance - 400; 
// 生成 重做日志 amount=400
update finance set amount = amount + 400;
commit;

对应的写redolog buffer以及redolog如下图:
在这里插入图片描述
当对数据表执行更新操作的时候,并不是直接写磁盘上的redolog文件,而是先写入redolog buffer即redolog缓冲区,然后在合适的时候刷新到磁盘,因为磁盘IO操作是很耗费性能的。
还需要注意的是,虽然上图第三步骤说的是“记录(balance=600)到redolog”,不能理解为在redolog中记录的就是对某条数据的逻辑修改。redolog是物理日志,记录的是对页的修改操作。

binary log

二进制日志,也称binlog,记录了对数据库只需更改的所有操作(不包括select和show这类操作),二进制日志的一个很重要的作用就是做数据恢复的时候用到,比如在一个数据库全备份文件恢复后,可以再通过二进制日志进行pinit-in-time的恢复。《如何根据binlog恢复数据》
和redolog不同的是,binlog是逻辑日志,记录的是对具体某行数据修改的逻辑。
知道了redolog和binlog的基本概念之后,进入正题。

redolog和binlog的区别
  • 二进制日志是在存储引擎的上层产生的,不管是什么存储引擎,对数据库进行了修改都会产生二进制日志。而redo log是innodb层产生的,只记录该存储引擎中表的修改。
  • 二进制日志记录操作的方法是逻辑性的语句。即便它是基于行格式的记录方式,其本质也还是逻辑的SQL设置,如该行记录的每列的值是多少。而redo log是在物理格式上的日志,它记录的是数据库中每个页的修改。
  • 因为二进制日志只在提交的时候一次性写入,所以二进制日志中的记录方式和提交顺序有关,且一次提交对应一次记录。而redo log中是记录的物理页的修改,redo log文件中同一个事务可能多次记录,最后一个提交的事务记录会覆盖所有未提交的事务记录。例如事务T1,可能在redo log中记录了 T1-1,T1-2,T1-3,T1* 共4个操作,其中 T1* 表示最后提交时的日志记录,所以对应的数据页最终状态是 T1* 对应的操作结果。而且redo log是并发写入的,不同事务之间的不同版本的记录会穿插写入到redo log文件中,例如可能redo log的记录方式如下: T1-1,T1-2,T2-1,T2-2,T2*,T1-3,T1*。
  • 事务日志记录的是物理页的情况,它具有幂等性,因此记录日志的方式极其简练。幂等性的意思是多次操作前后状态是一样的,例如新插入一行后又删除该行,前后状态没有变化。而二进制日志记录的是所有影响数据的操作,记录的内容较多。例如插入一行记录一次,删除该行又记录一次。

MySQL是如何做到异常重启还能保住数据的完整性

两阶段提交在这里插入图片描述

两阶段提交指的就是上图中的prepare阶段和commit阶段(注意不要把这个commit和事务中的commit混淆),即当更新操作写入内存后,会继续写入redolog日志缓冲区中,此时该日志记录属于预提交阶段,然后再写入binlog中,等事务提交的时候再将redolog的状态变为commit状态。

为什么要使用两阶段提交

假设当前ID=2的行,字段c的值是0,再假设执行update语句过程中在写完第一个日志后,第二个日志还没有写完期间发生了crash,会出现什么情况呢?

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

使用两阶段提交如何正确恢复数据

在时刻A崩溃
如果在图中时刻A的地方,也就是写入redo log 处于prepare阶段之后、写binlog之前,发生了崩溃(crash),由于此时binlog还没写,redo log也还没提交,所以崩溃恢复的时候,这个事务会回滚。这时候,binlog还没写,所以也不会传到备库。到这里,大家都可以理解。
在时刻B崩溃
binlog写完,redo log还没commit前发生crash,那崩溃恢复的时候MySQL会怎么处理?
先来看一下崩溃恢复时的判断规则:

  • 如果redo log里面的事务是完整的,也就是已经有了commit标识,则直接提交;
  • 如果redo log里面的事务只有完整的prepare,则判断对应的事务binlog是否存在并完整:
    a. 如果是,则提交事务;
    b. 否则,回滚事务。

这里说的在时刻B崩溃恢复指的就是2a步骤,因为binlog已经写完,从备库恢复到具体某个时间点的时候binlog中的记录肯定都会恢复到备库中,因此,肯定要把事务提交了,才能保证当前时间和备库恢复到时间点的时候数据是一样的。

数据更新到磁盘是由读取redo log的还是buffer pool

  1. 如果数据库正常运行,调入内存的数据页和磁盘的数据页不一致,这一页称为“脏页”。数据最终更新到磁盘中,就是直接把内存中的数据页写入磁盘,不需要用到redo log。
  2. 如果是数据库崩溃恢复的场景,InnoDB如果判断到一个数据页可能在崩溃恢复的时候丢失了更新,就会将该页读入内存,然后利用redo log更新内存中的数据,更新完后,内存也变为脏页,就回到了第一种状态。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值