一 背景
某个讨论群内,有朋友提出这样两个问题:
转文字:
1、那就是如果未提交的时候,redolog写满了,此时是阻塞还是覆盖呢
2、如果未提交然后写满了此时落盘了,你磁盘不是有脏数据了么,这二个问题不明白
关于问题大家在群内做了简单讨论,我再翻阅资料确认一些细节后,得到的分析结果如下。老规矩,先分析、分解问题,【先问是不是,再问为什么】,然后再去寻找、确定答案。
二 问题1
首先,分析了一下问题1的描述:
【未提交】指的是当前存在一个未提交的事务,【redolog写满了】指此时redolog文件此时是写满的状态,所以最终我们的问题是:
1-1 这个未提交事务是否会写redolog
1-2 如果要写redolog,是立即覆盖写入,还是需要阻塞一段时间,然后再写入。
1-1 未提交事务是否会写redolog
涉及事务提交流程和redolog的写入机制:
有binlog情况下,commit动作开始时,会有一个Redo XID 的动作记录写到redo,然后写data到binlog,binlog写成功后,会将binlog的filename,日志写的位置position再写到redo(position也会写到pos文件里),此时才表示该事务完成(committed)。如果只有XID,没有后面的filename和position,则表示事务为prepare状态。
事务提交流程:
commit; --> write XID to redo. --> write data to Binlog. --> write filename,postsion of binlog to redo. --> commited.
记录Binlog是在InnoDB引擎Prepare(即Redo Log写入磁盘)之后,这点至关重要。
crash发生在不同阶段时的事务状态和事务结果:
总结起来说就是如果一个事务在prepare阶段中落盘成功,并在MySQL Server层中的binlog也写入成功,那这个事务必定commit成功。但二者缺一不可。
不过上面描述还是没有明确,事务还没提交的时候,redolog 能不能被持久化到磁盘?
主要有三种可能的原因:
(1)InnoDB 有一个后台线程,每隔 1 秒轮询一次,具体的操作是这样的:调用 write 将 redolog buffer 中的日志写到文件系统的 page cache,然后调用 fsync 持久化到磁盘。而在事务执行中间过程的redolog 都是直接写在 redolog buffer 中的,也就是说,一个没有提交的事务的 redolog,也是有可能会被后台线程一起持久化到磁盘的;
(2)innodb_flush_log_at_trx_commit 设置是 1,也就是每次事务提交时,都执行 fsync 将 redolog 直接持久化到磁盘(设置为0表示每次事务提交的时候,都只是把 redolog 留在 redolog buffer 中;设置为2表示每次事务提交的时候,都只执行 write 将 redolog 写到文件系统的 page cache )。例如,假设事务 A 执行到一半,已经写了一些 redolog 到 redolog buffer 中,这时候有另外一个事务 B 提交,按照 innodb_flush_log_at_trx_commit = 1 的逻辑,事务 B 要把 redolog buffer 里的日志全部持久化到磁盘,这时候,就会带上事务 A 在 redolog buffer 里的日志一起持久化到磁盘;
(3)redolog buffer 占用的空间达到 innodb_log_buffer_size的一半大小时(默认是 8MB),后台线程会主动写盘。不过由于这个事务并没有提交,所以这个写盘动作只是 write 到了文件系统的 page cache,仍然是在内存中,并没有调用 fsync 真正落盘。
1-2 事务提交且当redolog文件满时,是否可以立即覆盖写入
如果确定需要写redo log文件,这时要看checkpoint。redo log文件是循环写入的,覆盖写之前,总要保证对应的(即将被覆盖的)脏页已经刷到了磁盘。所以如果要覆盖的脏页已经被刷到磁盘,那么久直接覆盖;如果还没刷到磁盘,就需要阻塞等待脏页完成刷到磁盘后再执行覆盖。
二 问题2
在事务提交之前,重做日志写入了redolog文件;但事务回滚了,那么后续是怎么处理的?
通过问题1的分析,可以得出结论,问题2中:【在事务提交之前,重做日志写入了redolog文件】是不存在的。至于事务回滚,是利用undo log来恢复/还原数据,当用户在commit之后通过rollback执行回滚时,或异常失败时,undo日志讲数据库“逻辑地”恢复到原来的样子(所有的修改都被逻辑取消,但数据结构和页本身在回滚之后可能大不相同)。我们将在后续文章中进行详细分析,事务的提交和回滚过程。
三 相关资料
借此问题,再回顾一下redolog的概念和写入机制(资料来自《MySQL技术内幕 InnoDB存储引擎 第2版》):
事务的执行过程中,生成的 redo log 先写redo log buffer,后根据需要落磁盘(redo log文件);
redo log 三种状态:
- 存在 redo log buffer 中,物理上是在 MySQL 进程内存中
- 写到磁盘(write),但是没有持久化(fsync),物理上是在文件系统的 page cache 里
- 持久化磁盘,对应的是 hard disk
日志写到 redo log buffer 是很快的,write 到 page cache 也差不多,但是持久化到磁盘的速度就慢多了。
InnoDB 提供了 innodb_flush_log_at_trx_commit 参数,取值如下:
- 设置为 0 时,表示每次事务提交时都只是把 redo log 留在 redo log buffer 中;
- 设置为 1 时,表示每次事务提交时都将 redo log 直接持久化到磁盘;
- 设置为 2 时,表示每次事务提交时都只是把 redo log 写到 page cache。
InnoDB 有一个后台线程,每隔 1 秒,就会把 redo log buffer 中的日志,调用 write 写到文件系统的 page cache,然后调用 fsync 持久化到磁盘。
更详细的相关资料可参考:
MySQL的WAL(Write-Ahead Logging)机制
MySQL · 引擎特性 · InnoDB redo log漫游