共计 7197 个字符,预计需要花费 18 分钟才能阅读完成。
在数据库系统中,既有存放数据的文件,也有存放日志的文件。日志在内存中也是有缓存 Log buffer,也有磁盘文件 log file,本文主要描述存放日志的文件。
MySQL 中的日志文件,有这么两类常常讨论到:undo 日志与 redo 日志。
undo 日志用于存放数据修改被修改前的值,假设修改 tba 表中 id= 2 的行数据,把 Name=’B’ 修改为 Name = ‘B2’,那么 undo 日志就会用来存放 Name=’B’ 的记录,如果这个修改出现异常,可以使用 undo 日志来实现回滚操作,保证事务的一致性。
对数据的变更操作,主要来自 INSERT UPDATE DELETE,而 UNDO LOG 中分为两种类型,一种是 INSERT_UNDO(INSERT 操作),记录插入的唯一键值;一种是 UPDATE_UNDO(包含 UPDATE 及 DELETE 操作),记录修改的唯一键值以及 old column 记录。
MySQL 跟 undo 有关的参数设置有这些:
mysql> show global variables like ‘%undo%’;
+————————–+————+
| Variable_name | Value |
+————————–+————+
| innodb_max_undo_log_size | 1073741824 |
| innodb_undo_directory | ./ |
| innodb_undo_log_truncate | OFF |
| innodb_undo_logs | 128 |
| innodb_undo_tablespaces | 3 |
+————————–+————+
mysql> show global variables like ‘%truncate%’;
+————————————–+——-+
| Variable_name | Value |
+————————————–+——-+
| innodb_purge_rseg_truncate_frequency | 128 |
| innodb_undo_log_truncate | OFF |
+————————————–+——-+
innodb_max_undo_log_size
控制最大 undo tablespace 文件的大小,当启动了 innodb_undo_log_truncate 时,undo tablespace 超过 innodb_max_undo_log_size 阀值时才会去尝试 truncate。该值默认大小为 1G,truncate 后的大小默认为 10M。
设置 undo 独立表空间个数,范围为 0 -128,默认为 0,0 表示表示不开启独立 undo 表空间 且 undo 日志存储在 ibdata 文件中。该参数只能在最开始初始化 MySQL 实例的时候指定,如果实例已创建,这个参数是不能变动的,如果在数据库配置文 件 .cnf 中指定 innodb_undo_tablespaces 的个数大于实例创建时的指定个数,则会启动失败,提示该参数设置有误。
如果设置了该参数为 n(n>0),那么就会在 undo 目录下创建 n 个 undo 文件(undo001,undo002 …… undo n),每个文件默认大小为 10M.
什么时候需要来设置这个参数呢?
当 DB 写压力较大时,可以设置独立 UNDO 表空间,把 UNDO LOG 从 ibdata 文件中分离开来,指定 innodb_undo_directory 目录存放,可以制定到高速磁盘上,加快 UNDO LOG 的读写性能。
InnoDB 的 purge 线程,根据 innodb_undo_log_truncate 设置开启或关闭、innodb_max_undo_log_size 的参数值,以及 truncate 的频率来进行空间回收和 undo file 的重新初始化。
该参数生效的前提是,已设置独立表空间且独立表空间个数大于等于 2 个。
purge 线程在 truncate undo log file 的过程中,需要检查该文件上是否还有活动事务,如果没有,需要把该 undo log file 标记为不可分配,这个时候,undo log 都会记录到其他文件上,所以至少需要 2 个独立表空间文件,才能进行 truncate 操作,标注不可分配后,会创建一个独立的文件 undo_<space_id>_trunc.log,记录现在正在 truncate 某个 undo log 文件,然后开始初始化 undo log file 到 10M,操作结束后,删除表示 truncate 动作的 undo_<space_id>_trunc.log 文件,这个文件保证了即使在 truncate 过程中发生了故障重启数据库服务,重启后,服务发现这个文件,也会继续完成 truncate 操作,删除文件结束后,标识该 undo log file 可分配。
- innodb_purge_rseg_truncate_frequency
用于控制 purge 回滚段的频度,默认为 128。假设设置为 n,则说明,当 Innodb Purge 操作的协调线程 purge 事务 128 次时,就会触发一次 History purge,检查当前的 undo log 表空间状态是否会触发 truncate。
如果需要设置独立表空间,需要在初始化数据库实例的时候,指定独立表空间的数量。
UNDO 内部由多个回滚段组成,即 Rollback segment,一共有 128 个,保存在 ibdata 系统表空间中,分别从 resg slot0 – resg slot127,每一个 resg slot,也就是每一个回滚段,内部由 1024 个 undo segment 组成。
回滚段(rollback segment)分配如下:
- slot 0,预留给系统表空间;
- slot 1- 32,预留给临时表空间,每次数据库重启的时候,都会重建临时表空间;
- slot33-127,如果有独立表空间,则预留给 UNDO 独立表空间;如果没有,则预留给系统表空间;
回滚段中除去 32 个提供给临时表事务使用,剩下的 128-32=96 个回滚段,可执行 96*1024 个并发事务操作,每个事务占用一个 undo segment slot,注意,如果事务中有临时表事务,还会在临时表空间中的 undo segment slot 再占用一个 undo segment slot,即占用 2 个 undo segment slot。如果错误日志中有:Cannot find a free slot for an undo log。
则说明并发的事务太多了,需要考虑下是否要分流业务。
回滚段(rollback segment)采用 轮询调度的方式来分配使用,如果设置了独立表空间,那么就不会使用系统表空间回滚段中 undo segment,而是使用独立表空间的,同时,如果回顾段正在 Truncate 操作,则不分配。
当数据库对数据做修改的时候,需要把数据页从磁盘读到 buffer pool 中,然后在 buffer pool 中进行修改,那么这个时候 buffer pool 中的数据页就与磁盘上的数据页内容不一致,称 buffer pool 的数据页为 dirty page 脏数据,如果这个时候发生非正常的 DB 服务重启,那么这些数据还没在内存,并没有同步到磁盘文件中(注意,同步到磁盘文件是个随机 IO),也就是会发生数据丢失,如果这个时候,能够在有一个文件,当 buffer pool 中的 data page 变更结束后,把相应修改记录记录到这个文件(注意,记录日志是顺序 IO),那么当 DB 服务发生 crash 的情况,恢复 DB 的时候,也可以根据这个文件的记录内容,重新应用到磁盘文件,数据保持一致。
这个文件就是 redo log,用于记录 数据修改后的记录,顺序记录。它可以带来这些好处:
- 当 buffer pool 中的 dirty page 还没有刷新到磁盘的时候,发生 crash,启动服务后,可通过 redo log 找到需要重新刷新到磁盘文件的记录;
- buffer pool 中的数据直接 flush 到 disk file,是一个随机 IO,效率较差,而把 buffer pool 中的数据记录到 redo log,是一个顺序 IO,可以提高事务提交的速度;
假设修改 tba 表中 id= 2 的行数据,把 Name=’B’ 修改为 Name = ‘B2’,那么 redo 日志就会用来存放 Name=’B2’ 的记录,如果这个修改在 flush 到磁盘文件时出现异常,可以使用 redo log 实现重做操作,保证事务的持久性。
这里注意下 redo log 跟 binary log 的区别,redo log 是存储引擎层产生的,而 binary log 是数据库层产生的。假设一个大事务,对 tba 做 10 万行的记录插入,在这个过程中,一直不断的往 redo log 顺序记录,而 binary log 不会记录,知道这个事务提交,才会一次写入到 binary log 文件中。binary log 的记录格式有 3 种:row,statement 跟 mixed,不同格式记录形式不一样。
- innodb_log_files_in_group
redo log 文件的个数,命名方式如:ib_logfile0,iblogfile1… iblogfilen。默认 2 个,最大 100 个。
文件设置大小,默认值为 48M,最大值为 512G,注意最大值指的是整个 redo log 系列文件之和,即(innodb_log_files_in_group * innodb_log_file_size)不能大于最大值 512G。
- innodb_log_group_home_dir
文件存放路径
Redo Log 缓存区,默认 8M,可设置 1 -8M。延迟事务日志写入磁盘,把 redo log 放到该缓冲区,然后根据 innodb_flush_log_at_trx_commit 参数的设置,再把日志从 buffer 中 flush 到磁盘中。
- innodb_flush_log_at_trx_commit
- innodb_flush_log_at_trx_commit=1,每次 commit 都会把 redo log 从 redo log buffer 写入到 system,并 fsync 刷新到磁盘文件中。
- innodb_flush_log_at_trx_commit=2,每次事务提交时 MySQL 会把日志从 redo log buffer 写入到 system,但只写入到 file system buffer,由系统内部来 fsync 到磁盘文件。如果数据库实例 crash,不会丢失 redo log,但是如果服务器 crash,由于 file system buffer 还来不及 fsync 到磁盘文件,所以会丢失这一部分的数据。
- innodb_flush_log_at_trx_commit=0,事务发生过程,日志一直激励在 redo log buffer 中,跟其他设置一样,但是在事务提交时,不产生 redo 写操作,而是 MySQL 内部每秒操作一次,从 redo log buffer,把数据写入到系统中去。如果发生 crash,即丢失 1s 内的事务修改操作。
- 注意:由于进程调度策略问题, 这个“每秒执行一次 flush(刷到磁盘)操作”并不是保证 100% 的“每秒”。
Redo log 文件以 ib_logfile[number]
命名,Redo log 以顺序的方式写入文件文件,写满时则回溯到第一个文件,进行覆盖写。(但在做 redo checkpoint 时,也会更新第一个日志文件的头部 checkpoint 标记,所以严格来讲也不算顺序写)。
实际上 redo log 有两部分组成:redo log buffer 跟 redo log file。buffer pool 中把数据修改情况记录到 redo log buffer,出现以下情况,再把 redo log 刷下到 redo log file:
- Redo log buffer 空间不足
- 事务提交(依赖 innodb_flush_log_at_trx_commit 参数设置)
- 后台线程
- 做 checkpoint
- 实例 shutdown
- binlog 切换
假设有 A、B 两个数据,值分别为 1,2,开始一个事务,事务的操作内容为:把 1 修改为 3,2 修改为 4,那么实际的记录如下(简化):
A. 事务开始.
B. 记录 A = 1 到 undo log.
C. 修改 A =3.
D. 记录 A = 3 到 redo log.
E. 记录 B = 2 到 undo log.
F. 修改 B =4.
G. 记录 B = 4 到 redo log.
H. 将 redo log 写入磁盘。
I. 事务提交
Undo + Redo 的设计主要考虑的是提升 IO 性能,增大数据库吞吐量。可以看出,B D E G H,均是新增操作,但是 B D E G 是缓冲到 buffer 区,只有 G 是增加了 IO 操作,为了保证 Redo Log 能够有比较好的 IO 性能,InnoDB 的 Redo Log 的设计有以下几个特点:
A. 尽量保持 Redo Log 存储在一段连续的空间上。因此在系统第一次启动时就会将日志文件的空间完全分配。以顺序追加的方式记录 Redo Log, 通过顺序 IO 来改善性能。
B. 批量写入日志。日志并不是直接写入文件,而是先写入 redo log buffer. 当需要将日志刷新到磁盘时 (如事务提交), 将许多日志一起写入磁盘.
C. 并发的事务共享 Redo Log 的存储空间,它们的 Redo Log 按语句的执行顺序,依次交替的记录在一起,
以减少日志占用的空间。例如,Redo Log 中的记录内容可能是这样的:
记录 1: <trx1, insert …>
记录 2: <trx2, update …>
记录 3: <trx1, delete …>
记录 4: <trx3, update …>
记录 5: <trx2, insert …>
D. 因为 C 的原因, 当一个事务将 Redo Log 写入磁盘时,也会将其他未提交的事务的日志写入磁盘。
E. Redo Log 上只进行顺序追加的操作,当一个事务需要回滚时,它的 Redo Log 记录也不会从 Redo Log 中删除掉。
前面说到未提交的事务和回滚了的事务也会记录 Redo Log,因此在进行恢复时, 这些事务要进行特殊的的处理。有 2 种不同的恢复策略:
A. 进行恢复时,只重做已经提交了的事务。
B. 进行恢复时,重做所有事务包括未提交的事务和回滚了的事务。然后通过 Undo Log 回滚那些
未提交的事务。
MySQL 数据库 InnoDB 存储引擎使用了 B 策略, InnoDB 存储引擎中的恢复机制有几个特点:
A. 在重做 Redo Log 时,并 不关心事务性。恢复时,没有 BEGIN,也没有 COMMIT,ROLLBACK 的行为。也不关心每个日志是哪个事务的。尽管事务 ID 等事务相关的内容会记入 Redo Log,这些内容只是被当作要操作的数据的一部分。
B. 使用 B 策略就必须要将 Undo Log 持久化,而且必须要在写 Redo Log 之前将对应的 Undo Log 写入磁盘。Undo 和 Redo Log 的这种关联,使得持久化变得复杂起来。为了降低复杂度,InnoDB 将 Undo Log 看作数据,因此记录 Undo Log 的操作也会记录到 redo log 中。这样 undo log 就可以象数据一样缓存起来,而不用在 redo log 之前写入磁盘了。
包含 Undo Log 操作的 Redo Log,看起来是这样的:
记录 1: <trx1, Undo log insert <undo_insert …>>
记录 2: <trx1, insert …>
记录 3: <trx2, Undo log insert <undo_update …>>
记录 4: <trx2, update …>
记录 5: <trx3, Undo log insert <undo_delete …>>
记录 6: <trx3, delete …>
C. 到这里,还有一个问题没有弄清楚。既然 Redo 没有事务性,那岂不是会重新执行被回滚了的事务?
确实是这样。同时 Innodb 也会将事务回滚时的操作也记录到 redo log 中。回滚操作本质上也是
对数据进行修改,因此回滚时对数据的操作也会记录到 Redo Log 中。
一个回滚了的事务的 Redo Log,看起来是这样的:
记录 1: <trx1, Undo log insert <undo_insert …>>
记录 2: <trx1, insert A…>
记录 3: <trx1, Undo log insert <undo_update …>>
记录 4: <trx1, update B…>
记录 5: <trx1, Undo log insert <undo_delete …>>
记录 6: <trx1, delete C…>
记录 7: <trx1, insert C>
记录 8: <trx1, update B to old value>
记录 9: <trx1, delete A>
一个被回滚了的事务在恢复时的操作就是先 redo 再 undo,因此不会破坏数据的一致性。
本文永久更新链接地址:http://www.linuxidc.com/Linux/2018-02/150739.htm