redo log

在mysql中数据更新的流程是:
1、执行器先从引擎中找到数据,如果在内存则直接返回,如果不在内存查询后返回。
2、执行器拿到数据后,会先修改数据,然后记录undolog,调用引擎接口重新写入数据。
3、引擎将数据更新到内存,同时写入redo log,此时处于prepare状态。
4、执行器生成这个操作的binlog。
5、执行器调用引擎的事务提交接口,将redo状态改成commit状态,更新完成。

为什么有两个状态?

通过两个状态的提交的方式,保证提交事务之后,两个日志都已经写入了,同时如果采用两阶段的方式中间如果服务发生崩溃的话:
1、redo还没写入之前崩溃,这时binlog也还没写入,恢复数据不受影响。
2、redo写好了prepare状态,binlog还没写时崩溃,这时redo处于prepare状态,还没有提交,恢复时事务会回滚,binlog也还没有记录,所以不会影响。
3、redo已经有了commit标识,则直接提交事务,同时因为binlog有记录,则恢复数据也不受影响。
4、redo写好了prepare状态,binlog写好了,但是redo还没有改成commit时崩溃了,这时会判断对应事务的binlog是否存在并完整:
(1)如果存在并完整则提交事务,这时恢复到事务提交之后的状态,因为binlog中有记录,所以恢复成功。
(2)如果binlog不存在或者不完整,这时会恢复到事务提交之前的状态,因为binlog中无记录或者不完整的记录不会生效,所以恢复也成功。

redo log底层实现

redo日志会把事务在执行过程中对数据库所做的所有修改都记录下来,属于物理日志,在之后系统崩溃重启后可以把事务所做的任何修改都恢复出来。
redo log(重做日志)是InnoDB存储引擎独有的,它让MySQL拥有了崩溃恢复能力。
比如 MySQL 实例挂了或宕机了,重启时,InnoDB存储引擎会使用redo log恢复数据,保证数据的持久性与完整性。
MySQL 中数据是以页为单位,你查询一条记录,会从硬盘把一页的数据加载出来,加载出来的数据叫数据页,会放入到 Buffer Pool 中。
后续的查询都是先从 Buffer Pool 中找,没有命中再去硬盘加载,减少硬盘 IO 开销,提升性能。
更新表数据的时候,也是如此,发现 Buffer Pool 里存在要更新的数据,就直接在 Buffer Pool 里更新(change buffer)。
然后会把“在某个数据页上做了什么修改”记录到重做日志缓存(redo log buffer)里,接着刷盘到 redo log 文件里。
redo log buffer 是承接在 redo log 前面的一块内存缓冲区域。引擎写 redo log 并不会直接写磁盘,而是写 redo log buffer,后期再由 redo log buffer 刷到磁盘。这里的 redo log buffer 是用户空间的缓冲区,写磁盘之前还经过了一层内核缓冲区(os buffer) 。
 
理想情况,事务一提交就会进行刷盘操作,但实际上,刷盘的时机是根据策略来进行的。
小贴士:每条 redo 记录由“表空间号+数据页号+偏移量+修改数据长度+具体修改的数据”组成。

刷盘时机

mysql写文件有2块缓存。一块是自己定义在内存的log buffer, 另一个是磁盘映射到内存的os cache。

mysql可以调用 flush主动将log buffer 刷新到磁盘内存映射,也可以调用 fsync 强制操作系同步磁盘映射文件到磁盘。还可以同时调用 flush + fsync, 将缓存直接落盘。

InnoDB 存储引擎为 redo log 的刷盘策略提供了 innodb_flush_log_at_trx_commit 参数,它支持三种策略:

  • innodb_flush_log_at_trx_commit = 0 就是每秒调用 flush + fsync ,定时器自己维护。
  • innodb_flush_log_at_trx_commit = 1 就是实时调用 flush + fsync 没法批处理。
  • innodb_flush_log_at_trx_commit = 2 就是实时flush ,定时 fsync 交给OS维护定时器。
 
innodb_flush_log_at_trx_commit 参数默认为1,也就是说当事务提交时会调用 fsync 对 redo log 进行刷盘。
另外,InnoDB 存储引擎有一个后台线程,每隔1 秒,就会把 redo log buffer 中的内容写到文件系统缓存(page cache),然后调用 fsync 刷盘。
也就是说,一个没有提交事务的 redo log 记录,也可能会刷盘。
为什么呢?
因为在事务执行过程 redo log 记录是会写入redo log buffer 中,这些 redo log 记录会被后台线程刷盘。
除了后台线程每秒1次的轮询操作,还有一种情况,当 redo log buffer 占用的空间即将达到 innodb_log_buffer_size 一半的时候,后台线程会主动刷盘。

下面是不同刷盘策略的流程图。

innodb_flush_log_at_trx_commit=0
为0时,如果MySQL挂了或宕机可能会有1秒数据的丢失。
innodb_flush_log_at_trx_commit=1
为1时, 只要事务提交成功,redo log记录就一定在硬盘里,不会有任何数据丢失。
如果事务执行期间MySQL挂了或宕机,这部分日志丢了,但是事务并没有提交,所以日志丢了也不会有损失。
innodb_flush_log_at_trx_commit=2
为2时, 只要事务提交成功,redo log buffer中的内容只写入文件系统缓存(page cache)。
如果仅仅只是MySQL挂了不会有任何数据丢失,但是宕机可能会有1秒数据的丢失。

日志文件组

硬盘上存储的 redo log 日志文件不只一个,而是以一个日志文件组的形式出现的,每个的redo日志文件大小都是一样的。
比如可以配置为一组4个文件,每个文件的大小是 1GB,整个 redo log 日志文件组可以记录4G的内容。
它采用的是环形数组形式,从头开始写,写到末尾又回到头循环写,如下图所示。
在个日志文件组中还有两个重要的属性,分别是 write pos、checkpoint
  • write pos 是当前记录的位置,一边写一边后移
  • checkpoint 是当前要擦除的位置,也是往后推移
每次刷盘 redo log 记录到日志文件组中,write pos 位置就会后移更新。
每次 MySQL 加载日志文件组恢复数据时,会清空加载过的 redo log 记录,并把 checkpoint 后移更新。
write pos 和 checkpoint 之间的还空着的部分可以用来写入新的 redo log 记录。
如果 write pos 追上 checkpoint ,表示日志文件组满了,这时候不能再写入新的 redo log 记录,MySQL 得停下来,清空一些记录,把 checkpoint 推进一下。

redo log 小结

相信大家都知道 redo log 的作用和它的刷盘时机、存储形式。
现在我们来思考一个问题: 只要每次把修改后的数据页直接刷盘不就好了,还有 redo log 什么事?
它们不都是刷盘么?差别在哪里?
1 Byte = 8bit
1 KB = 1024 Byte
1 MB = 1024 KB
1 GB = 1024 MB
1 TB = 1024 GB
实际上,数据页大小是16KB,刷盘比较耗时,可能就修改了数据页里的几Byte 数据,有必要把完整的数据页刷盘吗?
而且数据页刷盘是随机写,因为一个数据页对应的位置可能在硬盘文件的随机位置,所以性能是很差。
如果是写 redo log,一行记录可能就占几十Byte,只包含表空间号、数据页号、磁盘文件偏移量、更新值,再加上是顺序写,所以刷盘速度很快。
所以用 redo log 形式记录修改内容,性能会远远超过刷数据页的方式,这也让数据库的并发能力更强。

生产环境配置

innodb_flush_log_at_trx_commit=1

posted on 2023-03-26 23:38  zhengbiyu  阅读(44)  评论(0编辑  收藏  举报