坦克文档数据库回滚技术思想
文章概述
本文介绍了坦克文档数据库回滚技术思想。在软件开发中,数据库操作出错时,回滚是至关重要的一种技术。文章说明了原值与最后值的关系,以及备份和回滚的步骤。回滚失败时,需要详细说明哪些值回滚失败,以避免数据不一致的风险。最后,文章提到了备份元数据文件的重要性。
关键要点
1.数据库操作中的回滚是至关重要的技术之一。
2.回滚的本质是从最后值回到原值的过程。
3.回滚包括定位最后值和从备份区找回原值两个关键点。
4.回滚成功或失败都需要将日志打印出来,以便于错误处理。
5.在执行数据操作前,应该备份数据文件以防止意外损失。
一、引言
在软件开发的时候,经常需要访问数据库,而数据库操作出错的时候,回滚是至关重要的一种技术。今天我把我开发坦克文档数据的时候的产生的思想写成一篇文章,说明数据回滚的工作过程。这篇文章有些冗余,但为了诠释,我相信这冗余是必要的。
二、原值与最后值
源码1.tconf,这个源码的Tconf对象保存在元数据.tconf
的文件上。
=#",,《》{}“”!!「」[]
文档类名称=用户
位置簿文件最大保存记录数量=20
空位簿文件最大保存记录数量=20
段文件最大保存记录数量=20
最后的段文件=1
最后的段文件的记录数量=0
最大唯一码=0
对象总数=0
位置簿文件列表=「1」
空位簿文件列表=「1」
段文件列表=「1」
上面的Tconf对象对应的 go 结构体
layout 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)一个备份区保存原值
- (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
文件上。
如果没有错误,那么这个修改操作就成功了。现在我们模拟在第三个步骤把元数据文件删除(或者改名),模拟文件不存在,出现错误。把这个错误返回给调用者,就会触发回滚程序。
上面说过,回滚本身是处理错误来的,所以它还可以分为两种情况,回滚成功,回滚失败。不管它成功还是失败,就整个修改来说,它没有把元数据.tconf
的Tconf对象
的文档类名称
的值修改为用户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)的位置。
第二个步骤:错误处理。
最后,在执行数据操作(不是回滚,因为我认为备份文件不属于回滚的一部分,而是数据库操作的一部分)前要备份数据文件。 这就是我的思想。