设为首页 加入收藏

TOP

数据库原理 - 序列5 - 事务是如何实现的? - Undo Log解析(一)
2019-09-17 18:51:54 】 浏览:57
Tags:数据库 原理 序列 事务 如何 实现 Undo Log 解析

本文节选自作者书籍《软件架构设计:大型网站技术架构与业务架构融合之道》。
作者微信公众号:架构之道与术。公众号底部菜单有书友群可以加入,与作者和其他读者进行深入讨论。也可以在京东、天猫上购买纸质书籍。

6.6 事务实现原理之2:Undo Log

6.6.1 Undo Log是否一定需要

说到Undo Log,很多人想到的只是“事务回滚”。“事务回滚”有四种场景:
场景1:人为回滚。事务执行到一半时发生异常,客户端调用回滚,通知数据库回滚,数据库回滚成功。
场景 2:宕机回滚。事务执行到一半时数据库宕机,重启,需要回滚。
场景 3:人为回滚 + 宕机回滚。客户端调用回滚,数据库开始回滚数据,回滚到一半时数据库宕机,重启,继续回滚。
场景 4:宕机回滚 + 宕机回滚。宕机重启,在回滚的过程中再次宕机。

对于这四种场景的解决方法,在上文的ARIES算法已经给出了答案,其中要用到Redo和Undo Log。这里扩展一下,除了ARIES算法,是否还有其他的方法可以做事务回滚?或者说,Undo Log是否一定需要?
回滚,就是取消已经执行的操作。无论从物理上取消,还是从逻辑上取消,只要能达到目的即可。假设Page数据都在内存里面,每个事务执行,都只在内存中修改数据,必须等到事务Commit之后写完Redo Log,再把Page数据刷盘。在这种策略下,不需要Undo Log也能实现数据回滚!因为在这种数据刷盘策略下,正好利用了“内存断电消失”的特性,磁盘上存储的全部是已经提交的数据,宕机重启,内存中还未完成的事务自然被一笔勾销了!在这种策略之下,未提交的事务不会进入Redo Log;未提交的事务,也不会刷盘,全都在内存里面。
把这个展开,就是Page数据刷盘的四种策略,如表6-10所示。下面对这四种策略进行详细分析:

表6-10 Page数据刷盘的四种策略
在这里插入图片描述

No Steal和Steal:指未提交的事务是否可以写入磁盘中?No Steal是未提交的事务不能写入磁盘,只能在内存中操作,等到事务提交完,再把数据一次性写入;Steal是指未提交的事务也能写入,如果事务需要回滚,再更改磁盘上的数据。
No Force和Force: 是指已经提交的事务是否必须写入磁盘?No Force是指已经提交的事务可以保留在内存里,暂时不用写入磁盘;Force是指已经提交的事务必须强制写入磁盘。

策略1:Force和No Steal。已经提交的事务必须强制写入磁盘,未提交的事务,只能保留在内存里,等事务提交后再写入磁盘,这种策略不需要Redo Log和Undo Log,仅靠数据本身就能实现原子性和持久性。但很显然不可行,未提交的事务不能写入磁盘,这还可以接受;已提交的事务必须强制写入磁盘,这需要多次I/O,性能会受影响,所以才有了RedoLog。

策略2:No Force和No Steal。已提交的事务可以不立即写入磁盘,未提交的事务只能保留在内存里。在这个策略下,只需要Redo Log即可,因为有“内存断电消失”这个天然特性。

策略3:Force/Steal。已提交的事务立即写入磁盘,未提交的事务也立即写入磁盘。这种只需要UndoLog回滚宕机时未提交的事务,不需要Redo Log。但和策略1一样,显然不可行,多次I/O的性能会受影响。

策略4:No Force/Steal。第4种策略是我们最想要的,也是InnoDB实现的策略。就是已经提交的事务可以不立即写入磁盘;未提交的事务可以立即写入磁盘,也可以延迟写入磁盘!再通俗一点,无论事务是否提交,既可以立即写入磁盘,也可以不写,写入磁盘时机任意,想什么时候写就什么时候写。

策略1和策略3因为性能问题不能接受,所以必须要有Redo Log。而策略4和策略2都可以接受,但策略4比策略2好的地方在于提高了I/O效率。因为事务没有提交,就开始写入磁盘,等到提交事务的时候,要写入磁盘的数据量会小,不然要把所有数据都累积到事务提交时再一次性写入磁盘。
也正是因为现代的数据库用的都是第4种,是最灵活的一种数据刷盘策略。在这种策略下,为了实现事务的原子性和持久性,才有了如此复杂的Redo Log和Undo Log机制,才有了上面的ARIES算法。
除了在宕机恢复时对未提交的事务进行回滚,Undo Log还有两个核心作用:
(1)实现ACID中I(隔离性)。
(2)高并发。

6.6.2 Undo Log(MVCC)

在多线程编程中,读写的并发问题有三种策略,如表6-9所示。
表6-11 并发读写的三种策略
在这里插入图片描述
在JDK的JUC代码中,有CopyOnWriteArrayList和CopyOnWriteArraySet 两个类,有兴趣的读者可以阅读源码来理解CopyOnWrite的思想。
对比上面表格的三种并发策略可以知道,从上到下,并发度越来越高。而InnoDB用的就是CopyOnWrite思想,是在Undo Log里面实现的。每个事务修改记录之前,都会先把该记录拷贝一份出来,拷贝出来的这个备份存在Undo Log里。因为事务有唯一的编号ID,ID从小到大递增,每一次修改,就是一个版本,因此Undo Log维护了数据的从旧到新的每个版本,各个版本之间的记录通过链表串联。

也正因为每条记录都有多版本,才很容易实现事务ACID属性中的I(隔离性)。事务要并发,多个事务要读写同一条记录,为了实现第二个、第三个隔离级别,就不能让事务读取到正在修改的数据,而只能读取历史版本。
也正因为有了MVCC这种特性,通常的select语句都是不加锁的,读取的全部是数据的历史版本,从而支撑高并发的查询。这种读,专业术语叫作“快照读”,与之相对应的是“当前读”。表6-10列举了快照读和当前读对应的SQL语句,快照读就是最常用的select语句,当前读包括了加锁的select语句和insert/update/delete语句。
表6-12 快照读与当前读对应的SQL语句
在这里插入图片描述

6.6.3 Undo Log不是Log

了解Undo Log的功能后,进一步来看Undo Log的结构。其实Undo Log这个词有很大的迷惑性,它其实不是Log,而是数据。为什么这么说?
(1)Undo Log并不像Redo Log一样按照LSN的编号,从小到大依次执行append操作。Undo Log其实没有顺序,多个事务是并行地向Undo Log中随机写入的。
(2)一个事务一旦Commit之后,数据就“固化”了,固化之后不可能再回滚。这意味着Undo Log只在事务Commit过程中有用,一旦事务Commit了,就可以删掉UndoLog。具体来说:
对于insert记录,没有历史版本数据,因此insert的Undo Log只记录了该记录的主键ID,当事务提交之后,该Undo Log就可以删除了;
对于update/delete记录,因为MVCC的存在,其历史版本数据可能还被当前未提交的其他事务所引用,一旦未提交的事务提交了,其对应的Undo Log也就可以删除了。
所以,更应该把UndoLog叫作记录的“备份数据”,即在事务未提交之前的时间里的“备份数据”!提交事务后,没有其他事务引用历史版本了,就可以删除了。

下面来看这个“备份数据”是怎么操作的。如图6-18所示,Page中的每条记录,除了自身的主键ID和数据外,还有两个隐藏字段:一个是修改该记录的事务ID,一个是rollback_ptr,用来

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇mysql使用索引的注意事项 下一篇关于关系型数据库事务

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目