undo
1.1 undo
(记旧值)undo日志用于存放数据修改被修改前的值,假设修改 tba 表中 id=2的行数据,把Name=‘B’ 修改为Name = ‘B2’ ,那么undo日志就会用来存放Name='B’的记录,如果这个修改出现异常,可以使用undo日志来实现回滚操作,保证事务的一致性。
Id | Name |
---|---|
1 | A |
2 | B |
3 | C |
4 | D |
对数据的变更操作,主要来自 INSERT UPDATE DELETE,而UNDO LOG中分为两种类型,一种是 INSERT_UNDO(INSERT操作),记录插入的唯一键值;一种是 UPDATE_UNDO(包含UPDATE及DELETE操作),记录修改的唯一键值以及old column记录。
1.2 undo参数
代码语言:javascript复制mysql> show global variables like '%undo%';
-------------------------- ------------
| Variable_name | Value |
-------------------------- ------------
| innodb_max_undo_log_size | 1073741824 | -- 1G
| innodb_undo_directory | ./ |
| innodb_undo_log_truncate | OFF |
| innodb_undo_logs | 128 |
| innodb_undo_tablespaces | 0 |
-------------------------- ------------
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。
- innodb_undo_tablespaces:设置undo独立表空间个数,范围为0-128, 默认为0,0表示表示不开启独立undo表空间 且 undo日志存储在ibdata文件中。该参数只能在最开始初始化MySQL实例的时候指定,如果实例已创建,这个参数是不能变动的,如果在数据库配置文 件 .cnf 中指定innodb_undo_tablespaces 的个数大于实例创建时的指定个数,则会启动失败,提示该参数设置有误。
- innodb_undo_log_truncate: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。
1.3 undo空间管理
如果需要设置独立表空间,需要在初始化数据库实例的时候,指定独立表空间的数量。
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操作,则不分配。
上图展示了基本的Undo回滚段布局结构,其中:
- rseg0预留在系统表空间ibdata中;
- rseg 1~rseg 32这32个回滚段存放于临时表的系统表空间中;
- rseg33~ 则根据配置存放到独立undo表空间中(如果没有打开独立Undo表空间,则存放于ibdata中)
如果我们使用独立Undo tablespace,则总是从第一个Undo space开始轮询分配undo 回滚段。大多数情况下这是OK的,但假设我们将回滚段的个数从33开始依次递增配置到128,就可能导致所有的回滚段都存放在同一个undo space中。(参考函数trx_sys_create_rsegs 以及 bug#74471)
每个回滚段维护了一个段头页,在该page中又划分了1024个slot(TRX_RSEG_N_SLOTS),每个slot又对应到一个undo log对象,因此理论上InnoDB最多支持 96 * 1024个普通事务。
redo
2.1 redo
当数据库对数据做修改的时候,需要把数据页从磁盘读到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实现重做操作,保证事务的持久性。
Id | Name |
---|---|
1 | A |
2 | B |
3 | C |
4 | D |
这里注意下redo log 跟binary log 的区别,redo log 是存储引擎层产生的,而binary log是数据库层产生的。假设一个大事务,对tba做10万行的记录插入,在这个过程中,一直不断的往redo log顺序记录,而binary log不会记录,知道这个事务提交,才会一次写入到binary log文件中。binary log的记录格式有3种:row,statement跟mixed,不同格式记录形式不一样。
2.2 redo参数
- innodb_log_files_in_group:redo log 文件的个数,命名方式如:ib_logfile0,iblogfile1… iblogfilen。默认2个,最大100个。
- innodb_log_file_size:文件设置大小,默认值为 48M,最大值为512G,注意最大值指的是整个 redo log系列文件之和,即(innodb_log_files_in_group * innodb_log_file_size )不能大于最大值512G。
- innodb_log_group_home_dir:文件存放路径
- innodb_log_buffer_size: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%的“每秒”。
2.3 redo空间管理
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切换
2.4 redo和undo在事务中的应用
Undo Log
Undo Log 是为了实现事务的原子性,在MySQL数据库InnoDB存储引擎中,还用Undo Log来实现多版本并发控制(简称:MVCC)。
- 事务的原子性(Atomicity):事务中的所有操作,要么全部完成,要么不做任何操作,不能只做部分操作。如果在执行的过程中发生了错误,要回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过。
- 原理:Undo Log的原理很简单,为了满足事务的原子性,在操作任何数据之前,首先将数据备份到一个地方 (这个存储数据备份的地方称为Undo Log)。然后进行数据的修改。如果出现了错误或者用户执行ROLLBACK语句,系统可以利用Undo Log中的备份将数据恢复到事务开始之前的状态。除了可以保证事务的原子性,Undo Log也可以用来辅助完成事务的持久化。
- 事务的持久性(Durability):事务一旦完成,该事务对数据库所做的所有修改都会持久的保存到数据库中。为了保证持久性,数据库系统会将修改后的数据完全的记录到持久的存储上。
用Undo Log实现原子性和持久化的事务的简化过程
假设有A、B两个数据,值分别为1,2。
代码语言:javascript复制A.事务开始.
B.记录A=1到undo log.
C.修改A=3.
D.记录B=2到undo log.
E.修改B=4.
F.将undo log写到磁盘。
G.将数据写到磁盘。
H.事务提交
这里有一个隐含的前提条件:数据都是先读到内存中,然后修改内存中的数据,最后将数据写回磁盘。
之所以能同时保证原子性和持久化,是因为以下特点:
- 更新数据前记录Undo log。
- 为了保证持久性,必须将数据在事务提交前写到磁盘。只要事务成功提交,数据必然已经持久化。
- Undo log必须先于数据持久化到磁盘。如果在G,H之间系统崩溃,undo log是完整的,可以用来回滚事务。
- 如果在A-F之间系统崩溃,因为数据没有持久化到磁盘。所以磁盘上的数据还是保持在事务开始前的状态。
缺陷
每个事务提交前将数据和Undo Log写入磁盘,这样会导致大量的磁盘IO,因此性能很低。
如果能够将数据缓存一段时间,就能减少IO提高性能。但是这样就会丧失事务的持久性。因此引入了另外一 种机制来实现持久化,即Redo Log.
Redo Undo Log
原理:和Undo Log相反,Redo Log记录的是新数据的备份。在事务提交前,只要将Redo Log持久化即可, 不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是Redo Log已经持久化。系统可以根据 Redo Log的内容,将所有数据恢复到最新的状态。
Undo Redo事务的简化过程
假设有A、B两个数据,值分别为1,2
代码语言:javascript复制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事务的特点
- 为了保证持久性,必须在事务提交前将Redo Log持久化。
- 数据不需要在事务提交前写入磁盘,而是缓存在内存中。
- Redo Log 保证事务的持久性。
- Undo Log 保证事务的原子性。
- 有一个隐含的特点,数据必须要晚于redo log写入持久存储。
IO性能
Undo Redo的设计主要考虑的是提升IO性能。虽说通过缓存数据,减少了写数据的IO。但是却引入了新的IO,即写Redo Log的IO。如果Redo Log的IO性能不好,就不能起到提高性能的目的。为了保证Redo Log能够有比较好的IO性能,InnoDB 的 Redo Log的设计有以下几个特点:
恢复(Recovery)
恢复策略:前面说到未提交的事务和回滚了的事务也会记录Redo Log,因此在进行恢复时,这些事务要进行特殊的 的处理。有2中不同的恢复策略:
A. 进行恢复时,只重做已经提交了的事务。 B. 进行恢复时,重做所有事务包括未提交的事务和回滚了的事务。然后通过Undo Log回滚那些未提交的事务。
InnoDB存储引擎的恢复机制
MySQL数据库InnoDB存储引擎使用了B策略, InnoDB存储引擎中的恢复机制有几个特点:
InnoDB存储引擎中相关的函数 Redo: recv_recovery_from_checkpoint_start() Undo: recv_recovery_rollback_active() Undo Log的Redo Log: trx_undof_page_add_undo_rec_log()
参考
http://mysql.taobao.org/monthly/2015/04/01/ http://mysql.taobao.org/monthly/2016/07/01/ http://www.zhdba.com/mysqlops/2012/04/06/innodb-log1/ https://juejin.im/post/5c3c5c0451882525487c498d https://www.cnblogs.com/cchust/p/4439107.html https://segmentfault.com/a/1190000014810628 https://blog.csdn.net/joy0921/article/details/80130768 https://www.cnblogs.com/xinysu/p/6555082.html