参考文献

  • 千金良方: MySQL性能优化金字塔法则

redo log

  • redo log用来实现事务的持久性,即事务ACIDD.其由两部分组成:

    • 一是内存中的重做日志缓冲(redo log buffer),其是易失的
    • 二是重做日志文件(redo log file),其是持久的.
  • InnoDB是事务的存储引擎,其通过Force Log at Commit机制实现事务的持久性,即当事务提交(COMMIT)时,必须先将事务的所有日志写入到重做日志文件进行持久化,待事务的COMMIT操作完成才算完成.

  • redo log基本上都是顺序写的,在数据库运行时不需要对redo log的文件进行读取操作,而undo log是需要进行随机读写的.

  • WAL(Write-Ahead Logging),它的关键点就是先写日志,再写磁盘.

    • 当有一条记录需要更新的时候,InnoDB引擎就会先把记录写到redo log里面,并更新内存,这个时候更新算完成了.同时,InnoDB引擎会在合适的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做.
  • InnoDBredo log是固定大小的,比如可以配置一组4个文件,每个文件的大小为1G,那这个日志总共可以记录4GB的操作.从头开始写,写到末尾就又回到开头循环写.

    img
    • write pos是当前记录的位置,一边写一边后移,写到3号文件末尾就回到0号文件开头.
    • checkpoint是当前要擦除的位置,也就是往后推移并且循环的,擦除记录要把记录更新到数据文件.
    • write poscheckpoint之间的还有空着的部分,可以用用来记录新的操作.
    • 如果write pos追上checkpoint,表示redo log满了,这时不能再执行新的更新,得停下来先擦掉一些记录,把checkpoint推进一下.
  • 有了redo log,InnoDB就可以保证即使数据库发送异常重启,之前提交的记录都不会丢失,这个能力称为**crash-safe**.

  • redo log用于保证crash-safe能力.innodb_flush_log_at_trx_commit这个参数设置成1的时候,表示每次事务的redo log都持久化到磁盘.这个参数建议设置成1,这样可以保证MySQL异常重启之后数据不丢失.

  • redo log不是记录数据页"更新之后的状态",而是记录这个页"做了什么改动".

  • redo log文件: 文件名为ib_logfile+数字

    • 在 MySQL 8.0 中,重做日志文件的名称通常是ib_logfile0, ib_logfile1等, 但默认情况下,它们被拆分成更小的文件,存储在名为#innodb_redo的目录中

redo log作用

  • redo log称为重做日志,用来保证事务的原子性和持久性.
  • 快速提交
  • 恢复实例
  • 增量备份,以及恢复到某一时间点
  • 复制

redo log存储内容

  • 修改的物理数据页的编号以及相对于该页的字节偏移量.
  • 对数据页所做的修改操作,如插入、更新或删除操作.
  • 事务ID,即修改操作所属的事务编号.
  • 物理页修改前的状态,即修改前的数据,为了支持事务的回滚操作.
  • Checkpoint,用于记录哪些 redo log 已被持久化,以及哪些还没有被持久化,当数据库重启时可以根据 Checkpoint 来确定从哪里开始进行恢复.

redo log的写入方式

  • MySQL每执行一条DML(redo log的写入在事务开始后就会一直进行),会先把记录写入日志缓冲区redo log buffer,缓冲区的大小由
    innodb_log_buffer_size参数控制,后续某个时间点再一次性将多个操作记录写到redo log file.

    • 虽然事务没有提交,但相关的操作日志是有可能会被刷新到磁盘上的。事实上确实存在这种情况,解决的办法是:在提交事务时在redo log文件中添加一个commit标记表示对应的记录已经提交,这样即可实现快速提交。
  • 在计算机操作系统中,用户空间(user space)下的缓冲区数据,一般是无法直接写入磁盘的,必须经过操作系统内核空间缓冲区(即OS Buffer).

    • 日志最开始会写入位于存储引擎InnoDBredo log buffer,这个是在用户空间完成的.
    • 然后再将日志保存到操作系统内核空间的缓冲区(OS buffer)中.
    • 最后,通过系统调用fsync(),从OS buffer写入到磁盘上的redo log file中,完成写入操作.这个写入磁盘的操作,就叫做刷盘.

    img

  • MySQL提供参数innodb_flush_log_at_trx_commit用来控制重做日志刷新到磁盘的策略.

    参数值 作用
    0 称为延迟写,事务提交时不会将redo log buffer中日志写入到OS buffer,而是每秒写入OS buffer并调用写入到redo log file中.
    1 称为实时写,实时刷,事务每次提交都会将redo log buffer中的日志写入OS buffer并保存到redo log file中.
    2 称为实时写,延迟刷.每次事务提交写入到OS buffer,然后是每秒将日志写入到redo log file.
    1
    2
    3
    4
    5
    6
    7
    mysql> show variables like 'innodb_flush_log_at_trx_commit';
    +--------------------------------+-------+
    | Variable_name | Value |
    +--------------------------------+-------+
    | innodb_flush_log_at_trx_commit | 1 |
    +--------------------------------+-------+
    1 row in set (0.01 sec)

log block

  • InnoDB存储引擎中,重做日志都是以512字节进行存储的.这意味着重做日志缓冲,重做日志文件都是以块(block)的方式进行保存的,称之为重做日志块(redo log block),每块的大小为512字节.

redo logbinlog的区别

  • 记录内容不同.redo log主要记录了MySQL引擎发生的所有修改操作,用于将数据从缓存中刷新到磁盘中以保证数据的一致性;而binlog主要记录了数据库的所有写操作,如INSERT、UPDATE和DELETE等,主要用于数据恢复、数据同步和数据备份等操作.
  • 存储位置不同.redo log存储在InnoDB存储引擎的共享表空间中;binlog则存储在MySQL服务器的主机上,可以存储在不同的位置并且有多种存储格式可供选择.
  • 作用不同.redo log主要用于恢复数据,防止因MySQL崩溃或其他原因导致的数据丢失而无法恢复,因此是MySQL引擎内部的机制;binlog则主要用于将MySQL主数据库中的写操作同步到从数据库中,以实现数据同步和备份等功能,因此也是实现主从复制的关键.
  • 记录粒度不同.redo log记录的是每个数据页面的修改,粒度比较小;binlog则记录的是每个事务的执行语句,粒度较大.
  • redo log是循环写的,空间固定会用完;binlog是可以追加写入."追加写"是指binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志.

如何监控redo log的使用情况呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mysql > show engine innodb status\G

---
LOG
---
Log sequence number 21031617070
Log buffer assigned up to 21031617070
Log buffer completed up to 21031617070
Log written up to 21031617070
Log flushed up to 21031617070
Added dirty pages up to 21031617070
Pages flushed up to 21031617070
Last checkpoint at 21031617070
Log minimum file id is 6398
Log maximum file id is 6422
910767 log i/o's done, 0.00 log i/o's/second

1
2
3
4
5
6
7
8
mysql> select name,count from information_schema.innodb_metrics where name in ('log_lsn_current','log_lsn_last_checkpoint');
+-------------------------+-------+
| name | count |
+-------------------------+-------+
| log_lsn_last_checkpoint | 0 |
| log_lsn_current | 0 |
+-------------------------+-------+
2 rows in set (0.01 sec)
1
2
3
4
5
6
7
8
mysql> select * from sys.metrics where variable_name in ('log_lsn_current','log_lsn_last_checkpoint');
+-------------------------+----------------+----------------------+---------+
| Variable_name | Variable_value | Type | Enabled |
+-------------------------+----------------+----------------------+---------+
| log_lsn_current | 0 | InnoDB Metrics - log | NO |
| log_lsn_last_checkpoint | 0 | InnoDB Metrics - log | NO |
+-------------------------+----------------+----------------------+---------+
2 rows in set (0.19 sec)

redo log的使用量

1
2
3
4
use log = log_lsn_current - log_lsn_last_checkpoint (单位bytes)

use % = (use log / total log) * 100
= ((log_lsn_current - log_lsn_last_checkpoint)/(innodb_log_file_size * innodb_log_files_in_group)) * 100