在事务的实现机制上,MySQL 采用的是 WAL(Write-ahead logging,预写式
日志)机制来实现的。
在使用 WAL 的系统中,所有的修改都先被写入到日志中,然后再被应用到
系统中。通常包含 redo 和 undo 两部分信息。
redo log 称为重做日志,每当有操作时,在数据变更之前将操作写入 redo log,
这样当发生掉电之类的情况时系统可以在重启后继续操作。
undo log 称为撤销日志,当一些变更执行到一半无法完成时,可以根据撤销
日志恢复到变更之间的状态。
MySQL 中用 redo log 来在系统 Crash 重启之类的情况时修复数据(事务的持
久性),而 undo log 来保证事务的原子性。

问题
redolog与binlog的区别,为什么用redolog来恢复,而不用binlog来恢复?
区别:
redolog是innodb的,是物理日志,记录的某一页的做了哪些改变.数据落盘,就没用了,会被循环覆盖,主要用于innodb引擎恢复数据

binlog是mysql service层的,所有的引擎都有,记录的是逻辑日志(插入,删除,更新等),增量型日志,会不停的追加,通常适用于人工恢复数据,主从复制,监控binlog变化来同步其他库数据等(cloudCannal).

最重要的是,当数据库 crash 后,想要恢复未刷盘但已经写入 redo log 和
binlog 的数据到内存时,binlog 是无法恢复的。虽然 binlog 拥有全量的日志,
但没有一个标志让 innoDB 判断哪些数据已经入表(写入磁盘),哪些数据还没有。
image.png

image.png

日志类型

redo 日志设计大约有 53 种不同的类型日志
如最基本的
image.png
为什么会有这么多种类型?
修改一个数据数据库实际上会修改很多地方,比如 系统数据页面,用户数据页面(聚簇索引),页的数据比如fileheader,page header,page directory,所以会有很多种日志类型记录对那些地方做了修改

Mini-Transaction(对底层页面中的一次原子访问的过程)

一个语句在执行过程中会修改若干个页面,产生多条redo日志.在这个过程中redolog被认为划分为若干个不可分割的组
1、更新 Max Row ID 属性时产生的 redo 日志是不可分割的。
2、向聚簇索引对应 B+树的页面中插入一条记录时产生的 redo 日志是不可
分割的。
3、向某个二级索引对应 B+树的页面中插入一条记录时产生的 redo 日志是
不可分割的。
4、还有其他的一些对页面的访问操作时产生的 redo 日志是不可分割的….。

redo 日志的写入过程

redo log block 和日志缓冲区

InnoDB 为了更好的进行系统崩溃恢复,把通过 Mini-Transaction 生成的 redo
日志都放在了大小为 512 字节的块(block)中。。
为了解决磁盘速度过慢的问题而引入了 Buffer Pool。同理,写入 redo 日志时也不能直接直接写到磁盘上,实际上在服务器启动时就向操作 系统申请了一大片称之为 redo log buffer 的连续内存空间,翻译成中文就是 redo 日志缓冲区,我们也可以简称为 log buffer。这片内存空间被划分成若干个连续的 redo log block,我们可以通过启动参数 innodb_log_buffer_size 来指定 log buffer 的大小,该启动参数的默认值为 16MB。向 log buffer 中写入 redo 日志的过程是顺序的,也就是先往前边的 block 中
写,当该 block 的空闲空间用完之后再往下一个 block 中写。我们前边说过一个 Mini-Transaction 执行过程中可能产生若干条 redo 日志,这些 redo 日志是一个不可分割的组,所以其实并不是每生成一条 redo 日志,就将其插入到 log buffer 中,而是每个 Mini-Transaction 运行过程中产生的日志先暂时存到一个地方,当该 Mini-Transaction 结束的时候,将过程中产生的一组 redo日志再全部复制到 log buffer 中

redo 日志刷盘时机

我们前边说 Mini-Transaction 运行过程中产生的一组 redo 日志在
Mini-Transaction 结束时会被复制到 log buffer 中,可是这些日志总在内存里呆着
也不是个办法,在一些情况下它们会被刷新到磁盘里,比如:
1、log buffer 空间不足时,log buffer 的大小是有限的(通过系统变量 innodb_log_buffer_size 指定),如果不停的往这个有限大小的 log buffer 里塞入 日志,很快它就会被填满。InnoDB 认为如果当前写入 log buffer 的 redo 日志量已
经占满了 log buffer 总容量的大约一半左右,就需要把这些日志刷新到磁盘上。
2、事务提交时,我们前边说过之所以使用 redo 日志主要是因为它占用的空间少,还是顺序写,在事务提交时可以不把修改过的 Buffer Pool 页面刷新到磁盘,但是为了保证持久性,必须要把修改这些页面对应的 redo 日志刷新到磁盘。
3、后台有一个线程,大约每秒都会刷新一次 log buffer 中的 redo 日志到磁盘。
4、正常关闭服务器时等等。

redo 日志文件组

MySQL 的数据目录(使用 SHOW VARIABLES LIKE ‘datadir’查看)下默认有两
个名为 ib_logfile0 和 ib_logfile1 的文件,log buffer 中的日志默认情况下就是刷新
到这两个磁盘文件中。如果我们对默认的 redo 日志文件不满意,可以通过下边
几个启动参数来调节:
innodb_log_group_home_dir,该参数指定了 redo 日志文件所在的目录,默
认值就是当前的数据目录。
innodb_log_file_size, 该参数指定了每个 redo 日志文件的大小,默认值为 48MB,
innodb_log_files_in_group,该参数指定 redo 日志文件的个数,默认值为 2,最大值为 100。
所以磁盘上的 redo 日志文件可以不只一个,而是以一个日志文件组的形式出现的。这些文件以 ib_logfile[数字](数字可以是 0、1、2…)的形式进行命名。在将 redo 日志写入日志文件组时,是从 ib_logfile0 开始写,如果 ib_logfile0 写满
了,就接着 ib_logfile1 写,同理,ib_logfile1 写满了就去写 ib_logfile2,依此类推。
如果写到最后一个文件该咋办?那就重新转到 ib_logfile0 继续写。

Log Sequence Number

自系统开始运行,就不断的在修改页面,也就意味着会不断的生成 redo 日
志。redo 日志的量在不断的递增,就像人的年龄一样,自打出生起就不断递增永远不可能缩减了。
InnoDB 为记录已经写入的 redo 日志量,设计了一个称之为 Log Sequence Number 的全局变量,翻译过来就是:日志序列号,简称 LSN。规定初始的 lsn 值为 8704(也就是一条 redo 日志也没写入时,LSN 的值为 8704)。
我们知道在向 log buffer 中写入 redo 日志时不是一条一条写入的,而是以一个 Mini-Transaction 生成的一组 redo 日志为单位进行写入的。从上边的描述中可以看出来,每一组由 Mini-Transaction 生成的 redo 日志都有一个唯一的 LSN 值与
其对应,LSN 值越小,说明 redo 日志产生的越早。

flushed_to_disk_lsn

系统的 LSN 值有很多,LSN值是全局的,flushed_to_disk_lsn是落盘的,因此 两值相同时,说明全部落盘.

SHOW ENGINE INNODB STATUS\G 可以查看innodb存储引擎的各种LSN数据

innodb_flush_log_at_trx_commit 的用法

为了保证事务的持久性,用户线程在事务提交时需要将该事务执行过程中产生的所有 redo 日志都刷新到磁盘上。会很明显的降低数据库性能。如果对事务的持久性要求不是那么强烈的话,可以选择修改一个称为innodb_flush_log_at_trx_commit 的系统变量的值,该变量有 3 个可选的值
0:当该系统变量值为 0 时,表示在事务提交时不立即向磁盘中同步 redo 日
志,这个任务是交给后台线程做的。这样很明显会加快请求处理速度,但是如果事务提交后服务器挂了,后台线程没有及时将 redo 日志刷新到磁盘,那么该事务对页面的修改会丢失。
1:当该系统变量值为 1 时,表示在事务提交时需要将 redo 日志同步到磁盘,可以保证事务的持久性.1 也是 innodb_flush_log_at_trx_commit 的默认值
2:当该系统变量值为 2 时,表示在事务提交时需要将 redo 日志写到操作系统的缓冲区中,但并不需要保证将日志真正的刷新到磁盘。这种情况下如果数据库挂了,操作系统没挂的话,事务的持久性还是可以保证的,但是操作系统也挂了的话,那就不能保证持久性了。

恢复机制

在服务器不挂的情况下,redo 日志简直就是个大累赘,不仅没用,反而让
性能变得更差。但是万一数据库挂了,就可以在重启时根据 redo 日志中的记录
就可以将页面恢复到系统崩溃前的状态。
MySQL 可以根据 redo 日志中的各种 LSN 值,来确定恢复的起点和终点。然
后将 redo 日志中的数据,以哈希表的形式,将一个页面下的放到哈希表的一个
槽中。之后就可以遍历哈希表,因为对同一个页面进行修改的 redo 日志都放在
了一个槽里,所以可以一次性将一个页面修复好(避免了很多读取页面的随机 IO)。
并且通过各种机制,避免无谓的页面修复,比如已经刷新的页面,进而提升崩溃
恢复的速度。