6.5 事务实现原理之1:Redo Log

介绍事务怎么用后,下面探讨事务的实现原理。事务有ACID四个核心属性:
A:原子性。事务要么不执行,要么完全执行。如果执行到一半,宕机重启,已执行的一半要回滚回去。
C:一致性。各种约束条件,比如主键不能为空、参照完整性等。
I:隔离性。隔离性和并发性密切相关,因为如果事务全是串行的(第四个隔离级别),也不需要隔离。
D:持久性。这个很容易理解,一旦事务提交了,数据就不能丢。
在这四个属性中,D比较容易,C主要是由上层的各种规则来约束,也相对简单。而A和I牵涉并发问题、崩溃恢复的问题,将是讨论的重点。

说到事务的实现原理,会追溯到ARIES算法理论,ARIES(Algorithms for Recovery AndIsolation Expoliting Semantics)是20世纪90年代由IBM的几位研究员提出的一个算法集,主论文是ARIES: A TransactionRecovery Method Supporting Fine-Granularity Locking and Partial Rollbacks UsingWrite-Ahead Loggging。ARIES的思想影响深远,现代的关系型数据库(DB2、MySQL、InnoDB、SQL Server、Oracle)在事务实现的很多方面都吸收了该思想,在大学的教科书上如果讲到事务的实现,也都会介绍AREIS方法。
接下来,就以InnoDB为背景,分析事务的ACID其中的三个属性(A、I、D)是如何实现的。先从最简单的D开始(I/O问题),然后是A,最后讨论I。

6.5.1 Write-Ahead
一个事务要修改多张表的多条记录,多条记录分布在不同的Page里面,对应到磁盘的不同位置。如果每个事务都直接写磁盘,一次事务提交就要多次磁盘的随机I/O,性能达不到要求。怎么办呢?不写磁盘,在内存中进行事务提交。然后再通过后台线程,异步地把内存中的数据写入到磁盘中。但有个问题:机器宕机,内存中的数据还没来得及刷盘,数据就丢失了。
为此,就有了Write-aheadLog的思路:先在内存中提交事务,然后写日志(所谓的Write-ahead Log),然后后台任务把内存中的数据异步刷到磁盘。日志是顺序地在尾部Append,从而也就避免了一个事务发生多次磁盘随机I/O的问题。明明是先在内存中提交事务,后写的日志,为什么叫作Write-Ahead呢?这里的Ahead,其实是指相对于真正的数据刷到磁盘,因为是先写的日志,后把内存数据刷到磁盘,所以叫Write-Ahead Log。
内存操作数据 +Write-Ahead Log的这种思想非常普遍,后面讲LSM树的时候,还会再次提到这个思想。在多备份一致性中,复制状态机的模型也是基于此。
具体到InnoDB中,Write-Ahead Log是Redo Log。在InnoDB中,不光事务修改的数据库表数据是异步刷盘的,连Redo Log的写入本身也是异步的。如图6-7所示,在事务提交之后,Redo Log先写入到内存中的Redo Log Buffer中,然后异步地刷到磁盘上的Redo Log。
为此,InnoDB有个关键的参数innodb_flush_log_at_trx_commit控制Redo Log的刷盘策略,该参数有三个取值:
0:每秒刷一次磁盘,把Redo Log Buffer中的数据刷到Redo Log(默认为0)。
1:每提交一个事务,就刷一次磁盘(这个最安全)。
2:不刷盘。然后根据参数innodb_flush_log_at_timeout设置的值决定刷盘频率。
很显然,该参数设置为0或者2都可能丢失数据。设置为1最安全,但性能最差。InnoDB设置此参数,也是为了让应用在数据安全性和性能之间做一个权衡。

图6-7 Redo Log的异步刷盘示意图

6.5.2 Redo Log的逻辑与物理结构
知道了Redo Log的基本设计思想,下面来看Redo Log的详细结构。
从逻辑上来讲,日志就是一个无限延长的字节流,从数据库安装好并启动的时间点开始,日志便源源不断地追加,永无结束。
但从物理上来讲,日志不可能是一个永不结束的字节流,日志的物理结构和逻辑结构,有两个非常显著的差异点:
(1)磁盘的读取和写入都不是按一个个字节来处理的,磁盘是“块”设备,为了保证磁盘的I/O效率,都是整块地读取和写入。对于Redo Log来说,就是Redo Log Block,每个Redo Log Block是512字节。为什么是512字节呢?因为早期的磁盘,一个扇区(最细粒度的磁盘存储单位)就是存储512字节数据。
(2)日志文件不可能无限制膨胀,过了一定时期,之前的历史日志就不需要了,通俗地讲叫“归档”,专业术语是Checkpoint。所以Redo Log其实是一个固定大小的文件,循环使用,写到尾部之后,回到头部覆写(实际Redo Log是一组文件,但这里就当成一个大文件,不影响对原理的理解)。之所以能覆写,因为一旦Page数据刷到磁盘上,日志数据就没有存在的必要了。
图6-8展示了Redo Log逻辑与物理结构的差异,LSN(Log Sequence Number)是逻辑上日志按照时间顺序从小到大的编号。在InnoDB中,LSN是一个64位的整数,取的是从数据库安装启动开始,到当前所写入的总的日志字节数。实际上LSN没有从0开始,而是从8192开始,这个是InnoDB源代码里面的一个常量LOG_START_LSN。因为事务有大有小,每个事务产生的日志数据量是不一样的,所以日志是变长记录,因此LSN是单调递增的,但肯定不是呈单调连续递增。

图6-8 Redo Log逻辑结构与物理结构的差异

物理上面,一个固定的文件大小,每512个字节一个Block,循环使用。显然,很容易通过LSN换算出所属的Block。反过来,给定Redo Log,也很容易算出第一条日志在什么位置。假设在Redo Log中,从头到尾所记录的LSN依次如下所示:
(200,289,378,478,30,46,58,69,129)
很显然,第1条日志是30,最后1条日志是478,30以前的已经被覆盖。

6.5.3 Physiological Logging
知道了Redo Log的整体结构,下面进一步来看每个Log Block里面Log的存储格式。这个问题很关键,是数据库事务实现的一个核心点。
(1)记法1。类似Binlog的statement格式,记原始的SQL语句,insert/delete/update。
(2) 记法2。类似Binlog的RAW格式,记录每张表的每条记录的修改前的值、修改后的值,类似(表,行,修改前的值,修改后的值)。
(3) 记法3。记录修改的每个Page的字节数据。由于每个Page有16KB,记录这16KB里哪些部分被修改了。一个Page如果被修改了多个地方,就会有多条物理日志,如下所示:
(Page ID,offset1,len1,改之前的值,改之后的值)
(Page ID,offset2,len2,改之前的值,改之后的值)
前两种记法都是逻辑记法;第三种是物理记法。Redo Log采用了哪种记法呢?它采用了逻辑和物理的综合体,就是先以Page为单位记录日志,每个Page里面再采取逻辑记法(记录Page里面的哪一行被修改了)。这种记法有个专业术语,叫PhysiologicalLogging。
要搞清楚为什么要采用PhysiologicalLogging,就得知道逻辑日志和物理日志的对应关系:
(1)一条逻辑日志可能产生多个Page的物理日志。比如往某个表中插入一条记录,逻辑上是一条日志,但物理上可能会操作两个以上的Page?为什么呢,因为一个表可能有多个索引,每个索引都是一颗B+树,插入一条记录,同时更新多个索引,自然可能修改多个Page。
如果Redo Log采用逻辑日志的记法,一条记录牵涉的多个Page写到一半系统宕机了,要恢复的时候很难知道到底哪个Page写成功了,哪个失败了。
(2)即使1条逻辑日志只对应一个Page,也可能要修改这个Page的多个地方。因为一个Page里面的记录是用链表串联的,所以如果在中间插入一条记录,不仅要插入数据,还要修改记录前后的链表指针。对应到Page就是多个位置要修改,会产生多条物理日志。
所以纯粹的逻辑日志宕机后不好恢复;物理日志又太大,一条逻辑日志就可能对应多条物理日志。Physiological Logging综合了两种记法的优点,先以Page为单位记录日志,在每个Page里面再采用逻辑记法。

6.5.4 I/O写入的原子性(Double Write)
要实现事务的原子性,先得考虑磁盘I/O的原子性。一个LogBlock是512个字节。假设调用操作系统的一次Write,往磁盘上写入一个Log Block(512个字节),如果写到一半机器宕机后再重启,请问写入成功的字节数是0,还是[0,512]之间的任意一个数值?
这个问题的答案并不唯一,可能与操作系统底层和磁盘的机制有关,如果底层实现了512个字节写入的原子性,上层就不需要做什么事情;否则,在上层就需要考虑这个问题。假设底层没有保证512个字节的原子性,可以通过在日志中加入checksum解决。通过checksum能判断出宕机之后重启,一个Log Block是否完整。如果不完整,就可以丢弃这个LogBlock,对日志来说,就是做截断操作。
除了日志写入有原子性问题,数据写入的原子性问题更大。一个Page有16KB,往磁盘上刷盘,如果刷到一半系统宕机再重启,请问这个Page是什么状态?在这种情况下,Page既不是一个脏的Page,也不是一个干净的Page,而是一个损坏的Page。既然已经有Redo Log了,不能用Redo Log恢复这个Page吗?
因为Redo Log也恢复不了。因为Redo Log是Physiological Logging,里面只是一个对Page的修改的逻辑记录,Redo Log记录了哪个地方修改了,但不知道哪个地方损坏了。另外,即使为这个Page加了checksum,也只能判断出Page损坏了,只能丢弃,但无法恢复数据。有两个解决办法:
(1)让硬件支持16KB写入的原子性。要么写入0个字节,要么16KB全部成功。
(2)Doublewrite。把16KB写入到一个临时的磁盘位置,写入成功后再拷贝到目标磁盘位置。
这样,即使目标磁盘位置的16KB因为宕机被损坏了,还可以用备份去恢复。

Redo Log的原理比较复杂,在接下来的1篇中,将接着这个话题继续探讨。

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

数据库原理 - 序列3 - 事务是如何实现的? - Redo Log解析的更多相关文章

  1. 数据库原理 - 序列5 - 事务是如何实现的? - Undo Log解析

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

  2. 数据库原理 - 序列4 - 事务是如何实现的? - Redo Log解析(续)

    > 本文节选自<软件架构设计:大型网站技术架构与业务架构融合之道>第6.4章节. 作者微信公众号:> 架构之道与术.进入后,可以加入书友群,与作者和其他读者进行深入讨论.也可以 ...

  3. 数据库原理 - 序列7 - Binlog与主从复制

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

  4. 数据库中的两个最重要的日志redo log和binlog

    mysql整体来看其实只有两部分,一部分是server层,一部分是引擎层. 1.redo log(重做日志):当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写入redo log里面,并更新 ...

  5. [转]undo log与redo log原理分析

    数据库通常借助日志来实现事务,常见的有undo log.redo log,undo/redo log都能保证事务特性,这里主要是原子性和持久性,即事务相关的操作,要么全做,要么不做,并且修改的数据能得 ...

  6. InnoDB事务日志(redo log 和 undo log)详解

    数据库通常借助日志来实现事务,常见的有undo log.redo log,undo/redo log都能保证事务特性,undolog实现事务原子性,redolog实现事务的持久性. 为了最大程度避免数 ...

  7. 详细分析MySQL事务日志(redo log和undo log)

    innodb事务日志包括redo log和undo log.redo log是重做日志,提供前滚操作,undo log是回滚日志,提供回滚操作. undo log不是redo log的逆向过程,其实它 ...

  8. 【MySQL (六) | 详细分析MySQL事务日志redo log】

    Reference:  https://www.cnblogs.com/f-ck-need-u/archive/2018/05/08/9010872.html 引言 为了最大程度避免数据写入时 IO ...

  9. 详细分析MySQL事务日志(redo log和undo log) 表明了为何mysql不会丢数据

    innodb事务日志包括redo log和undo log.redo log是重做日志,提供前滚操作,undo log是回滚日志,提供回滚操作. undo log不是redo log的逆向过程,其实它 ...

随机推荐

  1. Tomcat配置与优化(内存、并发、管理)与性能监控

    原文链接:http://blog.csdn.net/xyang81/article/details/51530979 一.JVM内存配置优化 在开发当中,当一个项目比较大时,依赖的jar包通常比较多, ...

  2. 在Mac OS X中配置Apache + PHP + MySQL 很详细

    这是一篇超级详细的配置mac os下面php+mysql+apache的文章.非常详细我的大部分配置就是参考上面的内容的,比如,PHP不能连接数据库,就是改一下默认的php.ini中pdo_mysql ...

  3. meta 元标签的常用用法

    < meta > 元素 概要 标签提供关于HTML文档的元数据.元数据不会显示在页面上,但是对于机器是可读的.它可用于浏览器(如何显示内容或重新加载页面),搜索引擎(关键词),或其他 we ...

  4. strace详解及实战

    详细参数: -c 统计每一系统调用的所执行的时间,次数和出错的次数等. -d 输出strace关于标准错误的调试信息. -f 跟踪由fork调用所产生的子进程. -ff 如果提供-o filename ...

  5. SVN使用指引(Windows)

    SVN使用指引(本地服务器为Windows) 原:http://wiki.open.qq.com/wiki/SVN%E4%BD%BF%E7%94%A8%E6%8C%87%E5%BC%95%EF%BC% ...

  6. CSS布局十八般武艺都在这里了

    CSS布局十八般武艺都在这里了 Shelley Lee 4 个月前 布局是CSS中一个重要部分,本文总结了CSS布局中的常用技巧,包括常用的水平居中.垂直居中方法,以及单列布局.多列布局的多种实现方式 ...

  7. jQuery学习之旅 Item1 选择器【一】

    点击"名称"会跳转到此方法的jQuery官方说明文档. 1. 基础选择器 Basics 名称 说明 举例 #id 根据元素Id选择 $("divId") 选择I ...

  8. JavaScript Array+String对象的常用方法

    Array 对象 Array 对象用于在单个的变量中存储多个值. 创建 Array 对象的语法: new Array(); new Array(size); new Array(element0, e ...

  9. Java中的基本类型和引用类型变量的区别

    Java中的基本类型和引用类型变量的区别   学了一年多,说实话你要我说这些东西我是真说不出来是啥意思     基本类型: 基本类型自然不用说了,它的值就是一个数字,一个字符或一个布尔值. 引用类型: ...

  10. 发现了一个非常棒的pyqt5的例子集

    发现了一个非常棒的pyqt5的例子集 https://github.com/892768447/PyQt 各种各样的PyQt测试和例子 [Python3.4.4 or Python3.5][PyQt5 ...