REDO LOG 称为 重做日志 ,提供再写入操作,恢复提交事务修改的页操作,用来保证事务的持久性。UNDO LOG 称为 回滚日志 ,回滚行记录到某个特定版本,用来保证事务的原子性、一致性。
Innodb存储引擎是以页为单位来管理存储空间的. 在真正访问页面之前, 需要把在磁盘上的页缓存到内存中的缓冲池buffer pool中才可以访问. 所有的变更都必须先更新缓冲池中的数据, 然后缓冲池中的脏页会以一定的频率被刷入磁盘(checkPoint机制), 通过缓冲池来优化CPU和磁盘之间的鸿沟, 这样就可以保证整体的性能不会下降太快
redo 日志降低了刷盘频率redo 日志占用的空间非常小
redo 日志是顺序写入磁盘的在执行事物的过程中, 每执行一条语句, 就可能产生若干条redo日志, 这些日志是按照产生的顺序写入磁盘的, 也就是使用顺序IO, 效率比随机IO快事务执行过程中, redo log 不断记录redo log 跟 bin log 的区别, redo log 是存储引擎层产生的, 而bin log 是数据库层产生的. 假设一个事务, 对表做10万行的记录插入, 在这个过程中, 一直不断的往redo log 顺序记录, 而bin log 不会记录, 直到这个事务提交, 才会一次写入到bin log文件中
重做日志文件 (redo log file) ,保存在硬盘中,是持久的。
redo 日志文件如图所示, 其中的ib_logfile0和ib_logfile1即为redo日志
第 1 步:先将原始数据从磁盘中读入内存中来,修改数据的内存拷贝第 2 步:生成一条重做日志并写入 redo log buffer ,记录的是数据被修改后的值第 3 步:当事务 commit 时,将 redo log buffer 中的内容刷新到 redo log file ,对 redo log file 采用追加写的方式第 4 步:定期将内存中修改的数据刷新到磁盘中
体会:Write-Ahead Log(预先日志持久化):在持久化一个数据页之前,先将内存中相应的日志页持久化。
策略对应上图箭头3 ==下图箭头4 只有这步成功实现才真的保证了持久性
设置为 0 :表示每次事务提交时不进行刷盘操作。(系统默认 master thread 每隔 1s 进行一次重做日志的同步)设置为 1 :表示每次事务提交时都将进行同步,刷盘操作( 默认值 )设置为 2 :表示每次事务提交时都只把 redo log buffer 内容写入 page cache ,不进行同步。由 os 自己决定什么时候同步到磁盘文件。
另外, InnoDB存储引擎有一个后台线程, 每隔1秒, 就会把redo log buffer 中的内容写到文件系统缓存(page cache), 然后调用刷盘操作.
也就是说, 一个没有提交事务的redo log 记录, 也可能会刷盘. 因为在事务执行过程 redo log 记录是会写入redo log buffer 中, 这些redo log 记录会被后台线程刷盘
除了后台线程每秒1次的轮询操作, 还有一种情况, 当redo log buffer 占用空间即将达到innodb_log_buffer_size (这个参数默认是16M) 的一半的时候, 后台线程会主动刷盘
小结: 为1时, 只要事务提交成功, redo log 记录就一定在硬盘里, 不会有任何数据丢失
如果事务执行期间MySQL 挂了或宕机, 这部分日志丢了, 但是事务并没有提交, 所以日志丢了也不会有损失. 可以保证ACID的D(持久性), 数据绝对不会丢失, 但是效率最差的
为2时, 只要事务提交成功, redo log buffer 中的内容只写入文件系统缓存(page cache)
如果仅仅只是MySQL挂了不会有任何数据丢失, 但是操作系统宕机可能会有1秒数据的丢失, 这种情况下无法满足ACID中的D. 但数值2 肯定是效率更高的
MySQL把对底层页面的一次原子访问的过程称之为一个Mini-Transaction, 简称mtr, 比如, 向某个索引对应的B+树中插入一条记录的过程就是一个Mini-Transaction. 一个所谓的mtr可以包含一组redo日志, 在进行崩溃恢复时这组redo日志作为一个不可分割的整体
向log buffer 中写入redo日志的过程是顺序的, 也就是先往前边的block中写, 当该block的空闲空间用完之后再往下一个block中写. 当我们想往log buffer 中写入redo日志时, 第一个遇到的问题就是应该写在哪个block的哪个偏移量处, 所以InnoDB的设计者特意提供了一个称之为buf_free的全局变量, 该变量指明后续写入的redo日志应该写入到log buffer 中的哪个位置, 如图所示:
一个mtr执行过程中可能产生若干条redo日志, 这些redo日志是一个不可分割的组, 所以其实并不是每生成一条redo日志, 就将其插入到log buffer中, 而是每个mtr运行过程中产生的日志先暂时存到一个地方, 当该mtr结束的时候,将过程中产生的一组redo日志再全部复制到log buffer中. 我们现在假设有两个名为T1, T2的事务, 每个事务都包含2个mtr, 我们给这几个mtr命名一下:
事务T1的两个mtr分别成为mtr_T1_1和mtr_T1_2
事务T2的两个mtr分别成为mtr_T2_1和mtr_T2_2
每个mtr都会产生一组redo日志,用示意图来描述一下这些mtr产生的日志情况:
不同的事务可能是并发执行的, 所以T1, T2之间的mtr可能是交替执行的. 每当一个mtr执行完成时, 伴随该mtr生成的一组redo日志就需要被复制到log buffer 中, 也就是说不同事务的mtr可能是交替下入log buffer的, 画一个示意图(我们把一个mtr中产生的所有redo日志当作一个整体来画):
有的mtr产生的redo日志量非常大, 比如mtr_t1_2产生的redo日志占用空间比较大, 占用了三个block来存储
在整个日志文件组中还有两个重要的属性, 分别是write pos, checkpoint
write pos 是当前记录的位置, 一边写一边后移
checkpoint 是当前要擦除的位置, 也是往后推移
每次刷盘redo log 记录到日志文件组中, write pos 位置就会后移更新. 每次MySQL加载日志文件组恢复数据时, 会清空加载过的redo log 记录, 并把checkpoint后移更新. write pos 和 checkpoint之间的还空着的部分可以用来写入新的redo log 记录
相信大家都知道redo log 的作用和它的刷盘实际, 存储形式:
InnoDB的更新操作采用的时Write Ahead Log (预先日志持久化)策略, 即先写日志, 再写入磁盘
情况一:事务执行过程中可能遇到各种错误,比如 服务器本身的错误 , 操作系统错误 ,甚至是突然 断电 导致的错误。情况二:程序员可以在事务执行过程中手动输入 ROLLBACK 语句结束当前事务的执行。
比如, 当我们提交一条记录时, 当前页已经满了,因此必须新开辟数据页插入, 当执行回滚时只是删除新记录,不会把新数据页也删掉,因为其他事务也可能在新数据页上写入数据
1. 在 InnoDB1.1 版本之前 (不包括 1.1 版本),只有一个 rollback segment ,因此支持同时在线的事务限制为 1024 。虽然对绝大多数的应用来说都已经够用。2. 从 1.1 版本开始 InnoDB 支持最大 128 个 rollback segment ,故其支持同时在线的事务限制提高到了 128*1024 。
在1-7步骤的任意一步系统宕机,事务未提交, 该事务不会对磁盘上的数据做任何影响
在8-9 之间宕机, 恢复之后可以回滚, 也可以继续完成事务提交, 因为此时redo log 已经持久化
在9之后系统宕机, 内存映射中变更得数据还来不及刷回磁盘, 那么系统恢复之后, 可以根据redo log 把数据刷回磁盘
有了Redo Log和Undo Log之后:
在更新buffer pool中的数据之前, 我们需要先将该数据事务开始之前得状态写入Undo log 中, 假设更新到一半出错了 , 我们就可以通过undo log 来回滚到事务开始前
begin ;insert INTO user (name) values ( "tom" );
当我们执行UPDATE时:
对于更新得操作会产生update undo log ,并且会分更新主键和不更新主键的, 假设现在执行:
update user set name = "Sun" where id = 1;
这时会把老的记录写入新的undo log, 让回滚指针指向新的undo log, 他的undo no是1, 并且新的undo log 会指向老的undo log(undo no = 0 )
假设现在执行:
update user set id = 2 where id = 1;
对于更新主键的操作, 会先把原来的数据deletemark标识打开, 这时并没有真正的删除数据, 真正的删除会交给清理线程去判断, 然后再后面插入一条新的数据, 新的数据也会产生undo log , 并且undo log 的序号会递增.
可以发现每次对数据的变更都会产生一个undo log, 当一条记录被变更多次时, 那么就会产生多条undo log, undo log记录的是变更前的日志, 并且每个undo log 的序号是递增的, 那么当要回滚的时候, 按照序号依次向前推, 就可以找到我们的原始数据了
因为 insert 操作的记录,只对事务本身可见,对其他事务不可见。故该 undo log 可以在事务提交后直接删除,不需要进行purge 操作。
该undo log 可能需要提供 MVCC 机制,因此不能在事务提交时就进行删除。提交时放入 undo log 链表,等待purge 线程进行最后的删除。