Redo日志

Stella981
• 阅读 444

当向存储系统写一个数据元素时,通常是先写入主存或者缓冲,然后再写入磁盘,如果系统在写入磁盘的时候,系统发生故障,当系统恢复后,需要再次从磁盘中读取此数据元素的时候,并不知道此时磁盘上所保存的数据元素是正确的还是错误的,Redo日志是一种应对此种故障的比较常用的故障恢复策略。为了确保一个数据元素的完整性,还需要借助事务这一概念,对于更新数据一个元素的redo日志,我们将其描述为“在一个事物T中写入数据元素A的新值x”,使用元组<T, A, x>表示。

一个事务的数据元素的更新操作的redo日志,可以分成三个原语

  1. 开始事务T

  2. <T, A, x> 事务T写入数据元素A的新值x,这里,数据元素可能是一个数据块,一个记录,或者一个关系

  3. 提交事务T

第三个原语非常重要,通常认为,只有一个事务具有提交记录,表明这个事务被完整的执行,即,更新数据元素的操作已经执行成功。

在存储系统更新一个数据元素时,为了保证系统能从故障中恢复后,保证数据的完整性和一致性,采用下面的策略写redo日志到文件(非易失存储)。

  1. 事务T开始时,向redo日志写一个记录

  2. 当更新数据元素A之前(注意,此时数据元素可能位于磁盘,也可能位于内存中),先向redo日志文件中写一条<T, A, x>记录。

  3. 写入redo日志后,此更新操作才被反应到内存或者数据元素A的实际存储磁盘之中。

当系统从故障中恢复后,从redo日志文件头部开始需要扫描redo日志,以恢复数据,如果事务T具有记录,则把<T, A, x>中的新值x写入磁盘,如果此事务没有纪录,我们认为此事务没有执行成功,不把<T, A, x>中的新值x写入磁盘,并且向此redo日志添加一个记录,告诉系统,忽略此日志记录。这个过程叫重做或者重放,由于redo日志中保存的是一个数据元素的新值,因而,即使多次重做,和重做一次,在保证数据完整性和一致性的效果是相等的。

比如,从账户A转50元到账户B,一系列相关的操作和redo日志记录,以一个泳道图的方式,表示如下,

+----------------+-------+-------+----------------+
|      OP        |  M-A  |  M-B  |   REDO LOG     | 
+----------------+-------+-------+----------------+
|                |       |       | <BEGIN T1>     | (1)
| READ(A, a)     | 200   |       |                | (2)
| a = a - 50     |       |       |                | (3)
|                |       |       | <T1, A, 150>   | (4)
| OUTPUT(A, a)   | 150   |       |                | (5)
| READ(B, b)     |       | 200   |                | (6)
| b = b + 50     |       |       |                | (7)
|                |       |       | <T1, B, 250>   | (8)
| OUTPUT(B, b)   |       | 250   |                | (9)
|                |       |       | <COMMIT T1>    | (10) 
+----------------+-------+-------+----------------+
注:如果崩溃发生在步骤(10)之后,事务T1是完全可以被重做的,重做后,磁盘上的数据正是人们所期望的值。

为了避免恢复数据时,一直从头开始扫描redo日志文件,也为了避免redo日志文件长度永远不停增长,这里引入检查点这一个概念,日志管理器定期的检查redo日志,把日志记录中包含的新值写到其表示磁盘元素所应在的位置,对于系统恢复而言,可以忽略已经反映到磁盘中的事务redo日志记录

在检查redo日志的时候,一种简单的方式是检查点开始的时候,停止redo日志的更新,写入<START CKPT <t1, t2,,, tn>>, 其中,t1, t2,,, tn是当前活跃的事务,等到当前活跃的事务结束后,写入记录。

<BEGIN T1>
<T1, A, 150>
<BEGIN T2>
<START CKPT T1, T2>
<T2, C, 100>
<T2, D, 200>
<COMMIT T1>
<COMMIT T2>
<END CKPT>
<BEGIN T3>
<T3, A, 100>
<COMMIT T3>

有了检查点后,在恢复数据时,不需要从redo日志的头部开始重做所有日志记录,只需要从日志文件的尾部开始向后扫描,直至遇到第一个记录,此记录之前的redo日志由于已经被反映到磁盘上,因此不需要被再次重做,只需要重做此记录后的已提交的事务日志。

这种方式实现简单,但会造成日志管理在检查周期内不能增加新的事务,而影响整个系统的性能,一个在检查点内活动的、执行了很长时间的事务,会导致整个系统长时间不能接受新的事务。因此一种非静态检查点技术--不会阻塞检查点周期内日志更新,更适合实际使用,其遵循下面的规则:

  1. 检查点开始的时候,在日志文件中插入记录<START CKPT <t1, t2, ,, tn>>记录,其中,<t1, t2, ,, tn>是当前活动的事务。

  2. 在检查点周期内,如果此检查点所包含的活动事务全部提交,往日志文件尾部插入记录,若没有全部提交,则无需要提交记录,也无需等待,进入下一个检查点周期

    <T1, A, 150> <START CKPT, T1, T2>       (1) <T2, C, 100> <START CKPT, T1, T2, T3>   (2) <T2, D, 200> <START CKPT, T2, T3>       (3) <T3, A, 100> <START CKPT T2, T3>        (4)             (5)                  (6) <T4, B, 100> ...

为了说明非静态检查点的工作方式,我们让非静态检查点频繁的运行,在上面的例子中,检查点(1)开始时,有两个活动的事务,而在其开始后,系统启动了一个新的事务T3,检查点(1)结束后,T1和T2都未结束,根据规则,直接启动检查点(2),此时,有3个活动事务T1、T2和T3,当检查点(3)开始时,事务T1已经提交,因此,检查点(3)只检查事务T2和T3,但检查点(3)内,任何事务都没有提交,直到检查点(5)开始时,检查事务T3,随后系统新增加事务T4,检查点(5)之前,事务T3提交,因此,在日志文件插入,在此之后,新的redo日志继续插入到日志文件。

从非静态检查点的redo日志恢复数据时,仍然从日志的尾部开始,向后扫描,在遇到记录时,遵循下面的规则

  1. 继续后扫描,直到<START CKPT ... >记录处停止,然后从此处开始,向前重做redo日志

  2. 如果没有扫描到记录,则无法知道哪些事务已经被反映到磁盘中,则从日志文件的开始处,重做所有的日志

  3. 如果所有重做操作都是成功的,向日志文件写入一条记录

采用非静态检查点在恢复数据时会多扫描一些日志,但带来的好处是非常值得在实际应用中采用它。

由于向redo日志文件写redo日志时,可以采用只追加的方式,redo日志除了应对故障外,还可以带来一些额外的好处,当一个事务 的记录写入日志文件后,更新操作<T,  A, x>的新值可以先只反应到内存之中并且晚些时候把这个更新反应到磁盘之中,在把这些更新反应到磁盘中的时候,可以采用下面的策略,合并磁盘的操作

  1. 合并同若干个同时操作一个数据块的更新操作

  2. 采用一定的调度算法,按磁盘、磁片、磁道合并磁盘写操作

这 些更新操作只反应到内存而没有反应到磁盘之中的数据,被称为“胀数据”,当“胀数据”被驱赶出缓冲之前,“胀数据”包含对数据元素的更新需要立即被反映到 磁盘之中。如果存储系统设计在内存中维护了一个很大的数据缓冲,读取数据时,先从缓冲中读取,无疑会提高整个系统的性能。当然这里会引起另外一个话题,采用何种算法提高缓冲的命中率。

注:原创研究,若有转载,保留作者名称 @仪山湖

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
2年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
Python之time模块的时间戳、时间字符串格式化与转换
Python处理时间和时间戳的内置模块就有time,和datetime两个,本文先说time模块。关于时间戳的几个概念时间戳,根据1970年1月1日00:00:00开始按秒计算的偏移量。时间元组(struct_time),包含9个元素。 time.struct_time(tm_y
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
4个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这