文档数据库回滚技术思想

一、引言

在软件开发的时候,经常需要访问数据库,而数据库操作出错的时候,回滚是至关重要的一种技术。今天我把我开发坦克文档数据的时候的产生的思想写成一篇文章,说明数据回滚的工作过程。这篇文章有些冗余,但为了诠释,我相信这冗余是必要的。

二、原值与最后值

源码1.tconf,这个源码的Tconf对象保存在元数据.tconf的文件上。

=#",,《》{}“”!!「」[]	

文档类名称=用户
位置簿文件最大保存记录数量=20
空位簿文件最大保存记录数量=20
段文件最大保存记录数量=20
最后的段文件=1
最后的段文件的记录数量=0
最大唯一码=0
对象总数=0

位置簿文件列表=「1」
空位簿文件列表=「1」
段文件列表=「1」

上面的Tconf对象对应的 go 结构体

type MetaObj struct {
	DocClassName             string   `tconf:"文档类名称"`
	LocationFileMaxRecord    int      `tconf:"位置簿文件最大保存记录数量"`
	NullFileMaxRecord        int      `tconf:"空位簿文件最大保存记录数量"`
	SegmentFileMaxRecord     int      `tconf:"段文件最大保存记录数量"`
	LastSegmentFileName      string   `tconf:"最后的段文件"`
	LastSegmentFileRecordNum string   `tconf:"最后的段文件的记录数量"`
	LastID                   string   `tconf:"最大唯一码"`
	ItemObjTotal             string   `tconf:"对象总数"`
	LocationBookFileList     []string `tconf:"位置簿文件列表,忽略空数据"`
	NullBookFileList         []string `tconf:"空位簿文件列表,忽略空数据"`
	SegmentFileList          []string `tconf:"段文件列表,忽略空数据"`
}

这是一个Tconf对象,而原值是未修改的Tconf对象,也是实际上要还原的值。最后值是增加、删除和修改后得到的值。这两个是比较重要的概念。
增加、删除和修改都是从原值到最后值的过程。

为了诠释,可以这样理解:

增加数据可以理解为:空位原值到非空位最后值的过程。

删除数据可以理解为:非空位原值到空位最后值的过程。

修改数据可以理解为:非空位原值到非空位最后值的过程。

这样后就可以分解出了4个概念:空位原值、空位最后值、非空位原值、非空位最后值。这些概念,大家悟一下其中的意思。

三、回滚

回滚实际上是最后值到原值的过程。回滚有两个关键点:

定位最后值:定位到需要回滚的最后值。

备份:从备份区找回原值

  1. (1)一个备份区保存原值
  2. (2)另一个备份区保存最后值位置(实际上,在我的代码中并不是保存位置,即指针,而是保存值,这个括号里的文字先从记忆里删除,怕影响思维)

回滚本质就两个步骤:

第一个步骤:从(1)读取值写回(2)的位置。

第二个步骤:错误处理。

关于错误处理:回滚本身属于处理错误的一种方法,所以,在回滚开始的时候,甚至开始之前,就要把它作为错误日志的一部分记录起来。
数据库操作成功就不必说了,数据库操作失败就不能有例外把日志记录下来,即使冗余信息太多,因为数据的价值太大。
这里,拿坦克文档数据库实际代码来解释,为方便理解工作过程,不会展示代码细节。

func TestUpdateMeta(t *testing.T) {
	cls, err := Connect("用户", "./测试HOME/")
	if err != nil {
		fmt.Println(err)
		return
	}
    //把 源码1.tconf 的 文档类名称 的值修改为 用户1
	err = cls.MetaObj.SetDocClassName("用户1")
	if err != nil {
		fmt.Println(err)
		return
	}
}

当 SetDocClassName(“用户1”) 函数执行失败就会执行回滚。这个函数的内部流程有三步:

一、修改 MetaObj.DocClassName 为用户1.

二、转换为 Tconf对象。

三、然后把 Tconf对象保存到元数据.tconf文件上。

如果没有错误,那么这个修改操作就成功了。现在我们模拟在第三个步骤把元数据文件删除(或者改名),模拟文件不存在,出现错误。把这个错误返回给调用者,就会触发回滚程序。

上面说过,回滚本身是处理错误来的,所以它还可以分为两种情况,回滚成功,回滚失败。不管它成功还是失败,就整个修改来说,它没有把元数据.tconfTconf对象文档类名称的值修改为用户1来说,它是失败的。

不管成功与否,我们都要无例外把日志打印出来,因为数据的价值太大。在回滚前,我们就是把无例外的日志打印出来。我的单元测试日志是这样的:

元数据写入文件错误。机器消息:回滚错误测试。
准备回滚元数据,回滚内容是《
「文档类名称」原来的值是「用户」,要把「用户1」值还原为原来的值「用户」
「位置簿文件最大保存记录数量」原来的值是「20」,要把「20」值还原为原来的值「20」
「空位簿文件最大保存记录数量」原来的值是「20」,要把「20」值还原为原来的值「20」
「段文件最大保存记录数量」原来的值是「20」,要把「20」值还原为原来的值「20」
「最后的段文件」原来的值是「1」,要把「1」值还原为原来的值「1」
「最后的段文件的记录数量」原来的值是「0」,要把「0」值还原为原来的值「0」
「最大唯一码」原来的值是「0」,要把「0」值还原为原来的值「0」
「对象总数」原来的值是「0」,要把「0」值还原为原来的值「0」
「位置簿文件列表」原来的值是「1」,要把「1」值还原为原来的值「1」
「空位簿文件列表」原来的值是「1」,要把「1」值还原为原来的值「1」
「段文件列表」原来的值是「1」,要把「1」值还原为原来的值「1」
》
已经执行回滚,并成功了。
PASS

这个日志第一个部分(第一行)是告诉用户执行写入文件错误。第二个部分(在《》括号内的部分)告诉用户本次回滚的所有需要回滚的操作,由于我们只修改「文档类名称」,所以除「文档类名称」原来的值是「用户」,要把「用户1」值还原为原来的值「用户」这行日志外,其他的值都不变。其它是冗余的,但它也重要,因为数据的价值太大,所以我们必须打印出来。第三个部分,告诉用户回滚成了(即现在的文件元数据.tconf的数据和修改前一样)。

上面是回滚成功的情况,下面是回滚失败的情况:

元数据写入文件错误。机器消息:回滚错误测试。
准备回滚元数据,回滚内容是《
「文档类名称」原来的值是「用户」,要把「用户1」值还原为原来的值「用户」
「位置簿文件最大保存记录数量」原来的值是「20」,要把「20」值还原为原来的值「20」
「空位簿文件最大保存记录数量」原来的值是「20」,要把「20」值还原为原来的值「20」
「段文件最大保存记录数量」原来的值是「20」,要把「20」值还原为原来的值「20」
「最后的段文件」原来的值是「1」,要把「1」值还原为原来的值「1」
「最后的段文件的记录数量」原来的值是「0」,要把「0」值还原为原来的值「0」
「最大唯一码」原来的值是「0」,要把「0」值还原为原来的值「0」
「对象总数」原来的值是「0」,要把「0」值还原为原来的值「0」
「位置簿文件列表」原来的值是「1」,要把「1」值还原为原来的值「1」
「空位簿文件列表」原来的值是「1」,要把「1」值还原为原来的值「1」
「段文件列表」原来的值是「1」,要把「1」值还原为原来的值「1」
》
结束回滚元数据失败,回滚内容是《
「文档类名称」把修改后的值「用户1」回滚原来的值「用户」失败,务必注意原值是「用户」
「位置簿文件最大保存记录数量」把修改后的值「20」回滚原来的值「20」失败,务必注意原值是「20」
「空位簿文件最大保存记录数量」把修改后的值「20」回滚原来的值「20」失败,务必注意原值是「20」
「段文件最大保存记录数量」把修改后的值「20」回滚原来的值「20」失败,务必注意原值是「20」
「最后的段文件」把修改后的值「1」回滚原来的值「1」失败,务必注意原值是「1」
「最后的段文件的记录数量」把修改后的值「0」回滚原来的值「0」失败,务必注意原值是「0」
「最大唯一码」把修改后的值「0」回滚原来的值「0」失败,务必注意原值是「0」
「对象总数」把修改后的值「0」回滚原来的值「0」失败,务必注意原值是「0」
「位置簿文件列表」把修改后的值「1」回滚原来的值「1」失败,务必注意原值是「1」
「空位簿文件列表」把修改后的值「1」回滚原来的值「1」失败,务必注意原值是「1」
「段文件列表」把修改后的值「1」回滚原来的值「1」失败,务必注意原值是「1」
》
回滚元数据失败了,请查看日志,机器消息:回滚错误测试。
PASS

回滚失败日志第一部分和第二部分与回滚成功的日志一样。第三部分与第四部分不同。回滚失败也要把情况说明清楚,详细说明那些值回滚失败,这就是第三部分的日志的任务。第四部份总结告诉用户回滚已经失败了。

回滚失败意味什么:先说明 SetDocClassName("用户1") 的修改操作失败了,还进一步说明现在的元数据.tconf文件的数据和修改前的数据已经不一致了,这个风险就很大了。

我们刚才测试模拟的错误条件是:模拟文件不存在,这就意味着这个SetDocClassName("用户1")的操作导致了文件丢失,即元数据.tconf文件已经不存在硬盘上了。

四、最后的例外是备份

最后一个例外是备份元数据.tconf文件,在成功执行SetDocClassName("用户1")函数后删除备份文件。

// 修改文档类名称
func (m *MetaObj) SetDocClassName(name string) error {
	err := m.BackupFile()
	if err != nil {
		return err
	}
	m.CloneRollbackMetaObj()
	m.DocClassName = name
	m.CloneFinalMetaObj()
	err = m.SecureWriteFile()
	if err != nil {
		return err
	}
	return m.RemoveBackupFile()
}

这个代码是SetDocClassName("用户1")函数内部调用,可以清晰看见它的逻辑。BackupFile()是备份元数据.tconf文件;RemoveBackupFile()是删除元数据备份.tconf文件;m.CloneRollbackMetaObj()是把原值保存到备份区;m.CloneFinalMetaObj()是把最后值保存到备份区;m.SecureWriteFile()是把数据写入元数据.tconf文件,回滚逻辑也是封装在这个函数里面。

五、总结

回滚本质就两个步骤:

第一个步骤:从(1)读取值写回(2)的位置。

第二个步骤:错误处理。

最后,在执行数据操作(不是回滚,因为我认为备份文件不属于回滚的一部分,而是数据库操作的一部分)前要备份数据文件。
这就是我的思想。

  • 25
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值