上篇文章中,我们对MySQL的逻辑架构以及主要的存储引擎做了介绍,本文我们主要介绍一下日志系统。

WAL全称为Write-Ahead Logging,预写日志系统。其主要是指在执行写操作的时候并不是立刻更新到磁盘上,而是先记录在日志中,之后在合适的时间更新到磁盘中;日志主要分为undo log、redo log、binlog。

undo log

undo log 是存储引擎层的日志,用于撤销,回滚。当事务回滚或者数据库崩溃的时候,可以利用undo log 撤销未提交的事务对数据库产生的影响,简记:备份旧数据。

undo log产生和销毁:在事务开始前产生undo log;事务在提交时,并不会立刻删除undo log,innodb会将该事物对应的undo log放入到删除列表中,后面会通过后台线程purge thread进行回收。undo log属于逻辑日志,记录一个变化过程。

undo log 的作用:

  • 实现事务的原子性

  • 实现数据库的多版本并发控制(MVCC)

TIPS:

关于MVCC的知识,后面会单独写一篇文章详细展开。

redo log 是InnoDB引擎特有的,记录着事务里对数据的修改,具有Crash-Safe的能力;简记:备份新数据。它是用来搭配buffer pool,change buffer使用的;

buffer pool 主要用来减少磁盘的IO,缓存磁盘上的数据页;

change buffer的作用是将写操作先存在内存中,等到下次需要读取这些操作涉及到的数据页时,把数据页加载到buffer pool中。

redo log的作用就是持久化记录的写操作,防止在写操作更新到磁盘前发生断电丢失这些写操作,直到该操作对应的脏页真正落盘(先读取数据页到缓冲池然后应用写操作到缓冲池,最后再将脏页落盘替换磁盘上的数据页),该操作才会从 redo log 中移除(覆盖)。记录的是写操作对数据页的修改逻辑以及 change buffer的变化。

redo log 写入机制

在将写操作写入 redo log 的过程中并不是直接就进行磁盘IO来完成的,而是分为三个步骤。

image-1654764331670

1、写入 redo log buffer 中,这部分是属于MySQL 的内存中,是全局公用的。

2、在事务编写完成后,就可以执行 write 操作,写到文件系统的 page cache 中,属于操作系统的内存,如果 MySQL 崩溃不会影响,但如果机器断电则会丢失数据。

3、执行 fsync(持久化)操作,将 page cache 中的数据正式写入磁盘上的 redo log 中,也就是图中的 hard disk。

redo log 的持久化

1、持久化策略通过参数 innodb_flush_log_at_trx_commit 控制。

设置为 0 的时候,表示每次事务提交时都只是把 redo log 留在 redo log buffer 中 ; MySQL 崩溃就会丢失。

设置为 1 的时候,表示每次事务提交时都将 redo log 直接持久化到磁盘(将 redo log buffer 中的操作全部进行持久化,可能会包含其他事务还未提交的记录);断电也不会丢失。

设置为 2 的时候,表示每次事务提交时都只是把 redo log 写到 page cache。MySQL 崩溃不会丢失,断电会丢失。

2、InnoDB 后台还有一个线程会每隔一秒钟将 redo log buffer 中记录的操作执行 write 写到 page cache,然后再 fsync 到磁盘上。

未提交的事务操作也可能会持久化,未提交事务操作的持久化触发场景如下:

1、redo log buffer 被占用的空间达到 innodb_log_buffer_size(redo log buffer 大小参数)的一半时,后台会主动写盘,无论是否是已完成的事务操作都会执行。

2、innodb_flush_log_at_trx_commit 设为 1 时,在每次事务提交时,都会将 redo log buffer 中的所有操作(包括未提交事务的操作)都进行持久化。

3、后台有线程每秒清空 redo log buffer 进行落盘。

binlog

binlog 是Server层log,所有的存储引擎都可以使用;也是保存写操作的,但是它主要是用于进行集群中保证主从一致以及执行异常操作后恢复数据的。

binlog 的写入

image-1654765994450

binlog的写⼊逻辑与redo log 类似;

先写入到binlog cache,然后write,最后在fsync落盘。

write:从binglog cache写到 page cache。

fsync:将数据持久化到磁盘。

系统给binlog cache分配了⼀⽚内存,每个线程⼀个,参数 binlog_cache_size⽤于控制单个 线程内binlog cache所占内存的⼤⼩。

如果超过了这个参数规定的⼤⼩,就要暂存到磁盘。 事务提交的时候,执⾏器把binlog cache⾥的完整事务写⼊到binlog中,并清空binlog cache。

binlog 的持久化

binlog 的持久化策略通过参数 sync_binlog 控制:

sync_binlog=0 的时候,表示每次提交事务都只 write,不 fsync;

sync_binlog=1 的时候,表示每次提交事务都会执行 fsync;

sync_binlog=N(N>1) 的时候,表示每次提交事务都 write,但累积 N 个事务后才 fsync。

”三步提交“

image-1654766282116

其实就redo log而言,我们通常也称为两阶段提交,因为需要经历prepare 和 commit 两个阶段。

之前提到redo log有Crash-safe 能力,这指的是在机器突然断电重启后,之前的数据不会丢失,能够恢复成断电前状态的能力。redo log 拥有 crash-safe 能力,而 binlog 没有。

这是因为 redo log 记录的是未更新到磁盘上的操作,在断电后只需要将记录的操作数据更新到缓冲池中就可以了。而 binlog 记录的是所有请求过来的写操作,这个写操作在断电前有没有落盘并不知道。也正因为如此所以采用 redo log 与 binlog 的 “ 三步提交 ” 来保证 binlog 也具有 crash-safe 能力。

" 三步提交 " 过程是 " 写 redo log(prepare), binlog , redo log (commit) "。在断电重启后先检查 redo log 记录的事务操作是否为 commit 状态

1、如果是 commit 状态说明没有数据丢失,判断下一个。

2、如果是 prepare 状态,检查 binlog 记录的对应事务操作(redo log 与 binlog 记录的事务操作有一个共同字段 XID,redo log 就是通过这个字段找到 binlog 中对应的事务的)是否完整(这点在前面 binlog 三种格式分析过,每种格式记录的事务结尾都有特定的标识),如果完整就将 redo log 设为 commit 状态,然后结束;不完整就回滚 redo log 的事务,结束。

事务的特性

提到这个问题,相信大多数人可以脱口而出 ”ACID“;我们可以结合本文,梳理这四个特性是如何得到支持的。

A:原子性,事务的所有操作,要么全部完成,要么全部不完成,不会结束在某个中间环节。

通过undo log 得到支持,要么都成功,要么都失败。

C:一致性,事务开始之前和事务结束之后,数据库的完整性限制未被破坏。

通过其他几个特性,共同得到支持。

I:隔离性,当多个事务并发访问数据库中的统一数据时,所表现出来的相互关系。

MVCC

D:持久性,事务完成之后,事务所做的修改进行持久化保存,不会丢失。

通过redo log 得到支持