WAL机制
WAL预写式日志(Write-Ahead Logging,先行日志),就是先写日志,再写磁盘数据。既提高了性能,又保证数据的安全性。
MySQL中redo log就是采用WAL机制。
为什么WAL机制可以提高效率和安全性?
磁盘文件写操作是随机io,比较消耗性能。而写日志是顺序io,实际更新数据库文件的操作由后台线程根据log异步写入;此外存储表空间ID、页号、偏移量以及需要更新的值所需的存储空间也是很小的。WAL核心就是将随机写转换为顺序写,降低客户端的延时,提高了性能。
而redo log作为一个事务的原子性和持久性保障机制,提高了数据库安全性。
#redo log基本概念
redo log(重做日志):是innodb引擎特有的事务日志,用来保证事务的原子性和持久性。
redo log是物理日志,记录的是对磁盘上数据进行修改的相关信息,如对某个表空间的某个页的某个偏移量位置的某个值做了什么修改,修改值是多少。
redo log包含两部分:
- 内存中的日志缓冲区:redo log buffer,就在log buffer区域中。
- 磁盘上的日志文件:redo log file。
redo log有什么作用?
MySQL每执行一条DML语句,就会将记录写入到redolog buffer中。后续某个时间点再一次性将多个操作记录到redolog file。当故障发生导致内存数据丢失后,innodb会在重启时,经过重放redo将数据恢复到崩溃之前的状态。
redo log的格式
物理日志与逻辑日志的区别
1)物理日志:记录的是每个page页中具体存储的值是多少,在数据页上做了什么修改。比如某个事务将系统表空间中第100个页中偏移量1000处那个字节的值由1改为2。
2)逻辑日志:记录每一个page页中具体数据是怎么变动的,记录的是一个变动的过程,或者记录的就是SQL语句的逻辑。
redolog属于物理日志,由“表空间号+数据页号+偏移量+修改数据的长度+具体修改的数据“这几部分组成的。
redo log通用结构如图:
- type :该条 redo 日志的类型。如果是1则是写入1字节,8则是写入8字节,按写入数据修改的长度(多少)划分。
- space ID :表空间 ID。
- page number :页号。
- data :该条 redo 日志的具体内容。
根据在页面中写入数据的多少,把redo log类型做了划分,在5.7版本中就有50多种。
如MLOG_1BYTE类型,表示在页面的某个偏移量处写入1字节的redo日志。
这里以MLOG_8BYTE举个例子:
再以MLOG_WRITE_STRING举例子,表示在某个页面某个偏移量出写入一串数据,但是不能够确定具体占多少字节,需要在日志结构中添加len字段。
redo log的持久化
缓冲区的数据一般情况下无法直接写入磁盘,中间必须经过操作系统的缓冲区OS buffer,再通过系统调用fsync()函数将其刷到磁盘。
流程:日志数据—redolog buffer—OS buffer—redolog file
真正的落盘操作是需要执行fsync()才能完成的。
redolog buffer持久化到redo log可选的策略,有三种,通过innodb_flush_log_at_trx_commit设置
- 0:延迟写,速度最快,但不安全,一旦MySQL进程崩溃会导致上1秒的事务数据全部丢失。
- 1:实时写,实时刷(默认),最安全,不会丢失数据(一旦commit必在redolog文件中记录),但也是最慢的。
- 2:实时写,延时刷,速度较快,与0相比相对安全。只有在OS崩溃时,上1秒的事务会丢失。
事务还没提交的时候,redo log能不能被持久化到磁盘?
redo log数据存在三种状态
- 存在redolog buffer中
- 向磁盘写入,但还没真正落地到磁盘,而是保存在os buffer中
- 持久化到了磁盘redolog文件
触发真正的fsync()的场景:
- redolog buffer占用的空间即将达到innodb_log_buffer_size(默认8M)的50%时,后台线程会主动写盘。如果这个事务并没有提交,这个写盘动作只是写到了os buffer,并没有fsync()到磁盘redolog文件。
- 当innodb_flush_log_at_trx_commit设置为1时,并行的事务提交的时候,顺带会将某个未提交的事务redolog buffer持久到磁盘redolog文件。因为redo log buffer是共享的。
因此事务未提交时,redolog也是有可能被持久化到磁盘的,参考fsync()场景2。
redolog两阶段提交
redo log提交分为两步(两阶段提交),如下图所示
两阶段提交的具体过程包括:prepare阶段写redolog,binlog落盘,coomit阶段更新redolog(打标记)。
为什么要有两阶段提交?
主要是为了解决binlog和redolog一致性的问题。
使用两阶段提交的方式,无论数据库在哪个环节发生崩溃,都可以正确地恢复,因为redolog和binlog都会有一个唯一的XID来标记更新的事务,从而二者可以对齐。在崩溃恢复时,会顺序扫描redo log:
1)如果碰到既有prepare又有commit的redolog则直接提交(即阶段2),
2)如果redolog处于prepare状态,就会拿着xid去binlog中找对应的事务,判断事务所对应的binlog是否完整(即上图第4步binlog落盘是否完整),如果完整则事务提交,如果不完整则事务回滚。
网络上其他对崩溃恢复的描述:
- 情况一,在prepare阶段完成之后崩溃,由于binlog没有记录,因此直接回滚。
- 情况二,在binlog落盘后崩溃(上图第4步),检查事务是否完整无误,若是,直接提交即可,否则直接回滚。
- 情况三,在commit阶段完成后崩溃,同情况二。
整体看一个数据修改的事务流程:
参考:
https://www.cnblogs.com/kuangtf/articles/16353184.html