AnthonyZero's Bolg

MySQL笔记-三大日志概述

redo log

概述

redo log又叫重做日志,提供的是数据丢失后的前滚操作。
redo log是innodb引擎独有的日志,使用了 WAL 技术(Write-Ahead Logging),也就是预写日志。它的关键点就是先写日志,再写磁盘。对应到Mysql中具体操作,就是每次更新操作,先写日志,然后更新内存数据,最后等系统压力小的时候再进行IO更新磁盘数据。避免了每一次更新都需要进行IO操作。redo log 是保证了事务持久性的关键。

redo log 一般用在数据库恢复的情况:

  1. 如果是正常运行的实例的话,数据页被修改以后,跟磁盘的数据页不一致,称为脏页。最终数据落盘,就是把内存中的数据页写盘。这个过程,与 redo log 毫无关系。InnoDB会在后台刷脏页,而刷脏页的过程是要将内存页写入磁盘

    正常运行中的实例,数据写入后的最终落盘,是从redo log更新过来的还是从buffer pool更新过来的呢?
    实际上,redo log并没有记录数据页的完整数据,所以它并没有能力自己去更新磁盘数据页,也就不存在“数据最终落盘,是由redo log更新过去”的情况。

  2. 在崩溃恢复场景中,InnoDB如果判断到一个数据页可能在崩溃恢复的时候丢失了更新,就会将它读到内存,然后让redo log更新内存内容。更新完成后,内存页变成脏页,就回到了第一种情况的状态。

另外,redo log与undo log都被叫做事务日志。

组成

首先 redo log 包括两部分:

  • 内存中的日志缓冲(redo log buffer),该部分日志是易失性,的其中又分为三部分:Buffer Pool, Log Buffer,OS Buffer
  • 二是磁盘上的重做日志文件(redo log file),该部分日志是持久的
    由于有时候一次事务可能有多次更新,比如:
1
2
3
4
begin; 
insert into t1 …
insert into t2 …
commit;

redo log buffer就是一块内存,用来先存redo日志的。也就是说,在执行第一个insert的时候,数据的内存被修改了,redo log buffer也写入了日志。真正把日志写到redo log文件(文件名是 ib_logfile+数字),是在执行commit语句的时候做的

写入策略

我们知道InnoDB的数据是按数据页为单位来读写的。也就是说,当需要读一条记录的时候,并不是将这个记录本身从磁盘读出来,而是以页为单位,将其整体读入内存。在InnoDB中,每个数据页的大小默认是16KB

redo log是一个物理日志,数据库引擎加载是按“页”来的,redo log记录的就是每个“页”上的数据发生的变化,并没有记录数据页的完整数据。但是不像 binlog 那样,redo log 不记录 sql,而是以类似 session_id + date + file_id + block_id + 修改数据这样的格式去记录数据。

redo log的日志文件大小是根据配置固定的,如果有一组有四个文件,每个文件的大小是 1GB,那么总共就只能记录4GB的日志。
Alt text
因为redo log是前滚日志,也就是说一旦事务成功提交且数据持久化落盘之后,此时日志中的对应事务数据记录就失去了意义。所以redo log类似一个环形链表,从前往后写,到底了就删除最前面的再回到开头往后写。

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

bin log

binlog是MySQL的Server层实现的,所有引擎都可以使用。binlog 又叫二进制日志.它被用于记录 mysql 的数据更新(即使更新零条或者删除零条也会记录)。
binlog有三种工作模式:

  1. Row :日志中会记录每一行数据被修改的情况,然后在slave端对相同的数据进行修改。row格式会记录行的内容,记两条,更新前和更新后都有。
  2. Statement:每一条被修改数据的sql都会记录到 master 的 binlog 中,slave 在复制的时候sql进程会解析成和原来 master 端执行过的相同的sql再次执行。
  3. Mixed:结合了 Row 和 Statement 的优点,同时 binlog 结构也更复杂。

binlog的“归档”这个功能: 主要用于备份. 是追加写入的。“追加写”是指binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志

redo log 和 binlog之间的区别:

  • redo log是InnoDB引擎特有的;binlog是MySQL的Server层实现的,所有引擎都可以使用
  • binlog 记录的是每一行数据的变化或修改数据的 sql,redo log 记录的是数据页的变化。redo log是物理日志,记录的是“在某个数据页上做了什么修改”;binlog是逻辑日志,记录的是这个语句的原始逻辑,比如“给ID=2这一行的c字段加1 ”
  • binlog 能够实现归档功能, 是可以追加写入的,通过 binlog 可以实现备份,redo log 是循环写的(空间固定会用完),历史日志不会一直保留
  • mysql 高可用基于 binlog,像主从等系统机制都依赖于 binlog

undo log

undo log又叫回滚日志。事务未提交之前,undo log保存了未提交之前的版本数据,可作为数据旧版本快照供其他并发事务进行快照读。
因此,他能够提供两个功能:

  • 当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。简单的说:如果我们执行了insert操作,那么日志中就会新增一条相反的delete的sql;
  • 多行版本控制(MVCC):当读取的某一行被其他事务锁定时,它可以从undo log中分析出该行记录以前的数据是什么,从而提供该数据行的多版本信息,让用户实现非锁定一致性读取。

undo log保证了事务的原子性。

两阶段提交

Alt text
当innodb执行修改时,会经历一个两阶段提交的过程:

  • 执行器根据sql写入新数据,然后新数据更新到内存里
  • 将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。
  • 执行器生成这个操作的 binlog,并把 binlog 写入磁盘。
  • 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成

在两阶段提交的不同时刻,MySQL异常重启会出现什么现象:
图中时刻A的地方,也就是写入redo log 处于prepare阶段之后、写binlog之前,发生了崩溃(crash),由于此时binlog还没写,redo log也还没提交,所以崩溃恢复的时候,这个事务会回滚。这时候,binlog还没写,所以也不会传到备库。到这里,大家都可以理解.

主要集中在时刻B,也就是binlog写完,redo log还没commit前发生crash,那崩溃恢复的时候MySQL会怎么处理

  1. 如果redo log里面的事务是完整的,也就是已经有了commit标识,则直接提交 正常情况
  2. 如果redo log里面的事务只有完整的prepare,则判断对应的事务binlog是否存在并完整
    如果是,则提交事务(时刻B发生crash对应的就是这种情况,崩溃恢复过程中事务会被自动提交)。 否则,回滚事务

处于prepare阶段的redo log加上完整binlog,重启就能恢复,MySQL为什么要这么设计?
其实,这个问题还是跟我们在反证法中说到的数据与备份的一致性有关。在时刻B,也就是binlog写完以后MySQL发生崩溃,这时候binlog已经写入了(要考虑主从数据一致性,所以这里就不能回滚),之后就会被从库(或者用这个binlog恢复出来的库)使用。所以,在主库上也要提交这个事务。采用这个策略,主库和备库的数据就保证了一致性

两阶段提交是跨系统维持数据逻辑一致性时常用的一个方案,即使你不做数据库内核开发,日常开发中也有可能会用到。( 两阶段就是保证一致性用的。你不用担心日志写错,那样就是bug了)

总结

  1. redo log保证更新不丢失,支持的是事务的持久性
  2. undo log保证事务不成功可以回滚,支持的是事务的原子性
  3. redo log和binlog的二次提交机制,为事务的一致性提供了一定的保证