Redo log
基本概念
The redo log is a disk-based data structure used during crash recovery to correct data written by incomplete transactions
基于磁盘的数据结构,保证了崩溃恢复,保证已提交事务的恢复(防止丢掉内存数据),并且通过WAL加快了写入的效率。
特点
- 顺序的磁盘写入(因为是日志追加的方式),提高写入效率
- redo log 和buffer pool的合作:有了redo log之后,我们就可以放心的使用内存做缓存,不怕数据丢失,把实际的修改页的随机操作变成顺序的写操作追加
- WAL:Write-Ahead Logging
- 在事务对数据库进行修改之前,先将修改的操作记录到一个持久的日志文件中
- 崩溃恢复:恢复未持久化的内存数据
涉及概念
1. WAL
- Write-Ahead Logging ,先写日志,再写数据,在任何修改数据的操作之前,必须讲操作记录到独立的日志文件中,目的是确保数据的持久性和一致性。
- 日志一般记录的是变更内容,包括了数据修改的细节(新值和旧值)还有事务标识。
- 当系统发生崩溃的时候,会根据日志的记录,恢复到最近的一次一致的状态。
- Checkpoint,系统会将日志定期刷入数据库,这个点就是Checkpoint。
2. Redo Log Buffer
- 重做日志缓冲区: 一块内存区域,用于临时存放即将写入 redo log 文件的日志记录。 默认大小为 16MB,可以通过 innodb_log_buffer_size 参数配置。
3. Redo Log Files
- 磁盘上的物理文件,用于持久化存储 redo log 记录。InnoDB 默认使用一组循环写入的 redo log 文件,通常命名为 ib_logfile0, ib_logfile1 等。 默认情况下,在数据目录(data directory)下创建。 可以通过 innodb_log_group_home_dir 和 innodb_log_file_size, innodb_log_files_in_group 参数配置 redo log 文件的位置、大小和数量。
4. 两个指针
- checkpoint,系统最后一次(Buffer Pool 脏页)刷入磁盘的点。write pos,系统当前最新的记录点。当write pos追上checkpoint就表示没空间了,需要写回磁盘。
5. 脏页:
- Buffer Pool中与磁盘数据不一致的页
如何工作的
基于事务
- 事务开始: 当事务启动时,MySQL会为该事务分配一个唯一的事务ID,同时在内存中为该事务分配一个Redo Log Buffer。
- 数据修改:在事务执行过程中,当对数据进行修改(插入、更新、删除)时,这些修改操作会首先被写入到事务的Redo Log Buffer中。这是一个很快的操作,因为它涉及的是在内存中的缓冲区。
- 持久化磁盘:一旦Redo Log Buffer满了,或者当事务提交时,MySQL会将Redo Log Buffer中的日志记录写入到Redo Log文件中, 这个过程是在修改实际数据之前,也就是WAL。然后更新write pos,当前写入的位置。
- 更新Checkpoint:当Redo Log的记录被写入到数据页,更新Checkpoint,Checkpoint包括了LSN的信息,Checkpoint往前的都可以删了
- 文件循环:当一个Redo Log文件写满或达到一定条件时,MySQL会切换到下一个Redo Log文件
- Redo Log Buffer:存储事务中的修改操作的内存缓冲区,用于事务的持久性和恢复,异步写入磁盘,和磁盘的数据页没有直接的关系。
- InnoDB Buffer Pool:包含了表和索引的实际数据。从而减少了磁盘 I/O 操作,提高了数据读写的效率(刷新脏页)。
刷新策略 (由 innodb_flush_log_at_trx_commit 参数控制):
innodb_flush_log_at_trx_commit 参数控制着 redo log buffer 何时刷新到 redo log 文件(与刷新数据页无关)。它对事务的持久性和性能有重要影响,有三个可选值:
-
0: 事务提交时,redo log buffer 的内容不会立即刷新到 redo log 文件,而是依赖于 InnoDB 的后台线程 (master thread) 每秒刷新一次到 redo log 文件,但不会立即刷到磁盘(仅刷新到文件系统缓存)。 系统崩溃时,可能会丢失 上一秒 提交的事务。 性能最 好,但持久性最弱。
英文解释: At transaction commit, redo log buffer content is not immediately flushed to redo log files. Instead, it relies on InnoDB's background thread (master thread) to flush to redo log files every second, but not immediately to disk (only to file system cache). In case of a system crash, transactions committed in the last second may be lost. Best performance, weakest durability.
-
1: (默认值) 事务提交时,redo log buffer 的内容会立即刷新到 redo log 文件 (位于文件系统缓存中),并且立即将 redo log 文件刷新到磁盘 (fsync 操作)。 这是 ACID 属性的完全保证,也是 最安全 的设置,但 性能相对较低,因为每次事务提交都需要进行磁盘 I/O。
英文解释: (Default value) At transaction commit, redo log buffer content is immediately flushed to redo log files, and the redo log files are immediately flushed to disk (fsync operation). This provides full ACID property guarantee and is the safest setting, but with relatively lower performance because each transaction commit requires disk I/O.
-
2: 事务提交时,redo log buffer 的内容会立即刷新到 redo log 文件,但 redo log 文件只会刷新到文件系统缓存,而不是立即刷到磁盘。 刷到磁盘的操作由操作系统控制,通常是周期性的。 相比于 innodb_flush_log_at_trx_commit=1,性能有所提升,但如果操作系统或硬件崩溃 (例如,断电),可能会丢失 文件系统缓存中 的 redo log 记录,从而导致 最后一次刷新到磁盘之后 提交的事务丢失。 性能和持久性介于 0 和 1 之间。
英文解释: At transaction commit, redo log buffer content is immediately flushed to redo log files, but the redo log files are only flushed to the file system cache, not immediately to disk. Flushing to disk is controlled by the operating system, usually periodically. Compared to innodb_flush_log_at_trx_commit=1, performance is improved, but if the operating system or hardware crashes (e.g., power outage), redo log records in the file system cache may be lost, potentially leading to the loss of transactions committed after the last flush to disk. Performance and durability are between 0 and 1.
-
其他的时机:write pos追上checkpoint
通常建议使用 innodb_flush_log_at_trx_commit=1,以确保数据安全和事务的持久性。 在对数据安全性要求不高,但对性能要求极高的场景下,可以考虑使用 innodb_flush_log_at_trx_commit=2 或 0,但需要权衡数据丢失的风险
崩溃恢复的流程
- 通过redo log找到最近一次checkpoint的位置,然后根据checkpoint相对应的LSN开始,获取需要重做的日志。
- 确定需要回滚和需要提交的事务id,XID(Transaction ID)
- redo log回放,读取记录并应用到已提交的事务
- 找到 XID: 从 prepare 记录中找到 XID,即事务标识符
- 如果碰到既有prepare、又有commit的redo log,就直接提交
- 如果碰到只有prepare、而没有commit的redo log,就拿着XID去binlog找对应的事务,查看binlog是否完整
- 如果完整,则重做,不完整则撤销
文件结构
- 记录了数据页的物理修改
- Ordinary redo log files
- 普通的redo log文件,正在使用的,使用#ib_redoN命名
- Spare redo log files
- 空闲的redo log文件,使用#ib_redoN_tmp命名
'#ib_redo582' '#ib_redo590' '#ib_redo598' '#ib_redo606_tmp'
'#ib_redo583' '#ib_redo591' '#ib_redo599' '#ib_redo607_tmp'
'#ib_redo584' '#ib_redo592' '#ib_redo600' '#ib_redo608_tmp'
'#ib_redo585' '#ib_redo593' '#ib_redo601' '#ib_redo609_tmp'
'#ib_redo586' '#ib_redo594' '#ib_redo602' '#ib_redo610_tmp'
'#ib_redo587' '#ib_redo595' '#ib_redo603_tmp' '#ib_redo611_tmp'
'#ib_redo588' '#ib_redo596' '#ib_redo604_tmp' '#ib_redo612_tmp'
'#ib_redo589' '#ib_redo597' '#ib_redo605_tmp' '#ib_redo613_tmp'
优化
- todo
Q&A
- 为什么只用binlog不行
- binlog不知道数据页刷入磁盘对应的点(redolog的checkpoint)
- 如果恢复的话,只能恢复没有提交的数据,先回滚(undo log)再应用
- 只用redo log的问题
- 生态问题,很多中间件用到了binlog
- 没法记录历史数据,只有内存的数据,起不到归档的作用
配置
# --- MySQL 8.0.30 or Higher ---
# redo log的磁盘空间,默认值: 104857600 字节 (100MB)。最大重做日志容量为128GB。
innodb_redo_log_capacity = 8589934592;
# --- Before MySQL 8.0.30 ---
# 每个日志文件的大小
innodb_log_file_size = 256M
# 日志文件的数量
innodb_log_files_in_group = 2
# redo log buffer的大小,默认值为 16MB,如果经常执行大的事务,那不妨增大这个值,这样就可以在事务提交之前避免写入磁盘
# The size in bytes of the buffer that InnoDB uses to write to the log files on disk. The default is 16MB. A large log buffer enables large transactions to run without the need to write the log to disk before the transactions commit.
# Thus, if you have transactions that update, insert, or delete many rows, making the log buffer larger saves disk I/O
innodb_log_buffer_size = 16MB
# 刷入磁盘的时机
# 1:每次事务提交时都会将日志写入并刷新到磁盘。(默认)
# 0:每秒将日志写入并刷新到磁盘一次。未刷新日志的事务可能会在崩溃中丢失。
# 2:日志会在每次事务提交后写入(把 redo log buffer 里的数据写入到 os buffer 中,但不立即执行fsync () ),并每秒刷新到磁盘一次。未刷新日志的事务可能会在崩溃中丢失。
innodb_flush_log_at_trx_commit = 1
# redo log的目录
innodb_log_group_home_dir =
查询
--redo log capacity resize mechanism的状态
SHOW STATUS LIKE 'Innodb_redo_log_resize_status';
+-------------------------------+-------+
| Variable_name | Value |
+-------------------------------+-------+
| Innodb_redo_log_resize_status | OK |
+-------------------------------+-------+
--redo log 容量限制
mysql> SHOW STATUS LIKE 'Innodb_redo_log_capacity_resized';
+----------------------------------+-----------+
| Variable_name | Value |
+----------------------------------+-----------+
| Innodb_redo_log_capacity_resized | 104857600 |
+----------------------------------+-----------+