• 如何通过日志恢复被删除的数据


    如何通过日志恢复被删除的数据

    学习代码规范很重要


    我们经常会遇到因为误操作把某个表数据删除掉的情况。例如指定Delete语句时没有写where条件。

    发生这种情况一般都是很严重的,如果是生产数据将会造成很严重的后果。恰巧数据又是基础数据则雪上加霜。

    本小节会介绍,我们误操作删表时怎么处理。如何恢复被删除的表数据。

    本节只是抛砖引玉,具体完善实现细节可根据此代码个性化定制。

    模拟删表操作

    1. 首先模拟一下数据先给M.T.Person添加100条随机数据。
    USER>w ##class(M.T.Person).Populate(100)
    100
    USER>
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    在这里插入图片描述

    1. 执行删表操作。
    /// w ##class(M.M110).DeleteTable("M_T.Person")
    ClassMethod DeleteTable(tableName)
    {
    	&sql(Delete M_T.Person )
    	q SQLCODE
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    USER>w ##class(M.M110).DeleteTable("M_T.Person")
    0
    
    • 1
    • 2
    1. 再次查询表,确认数据是否删除。

    在这里插入图片描述

    如何恢复数据

    在恢复数据之前,首先要学会如何查看日志。可以查看百讲109讲。

    在这里插入图片描述

    • 找到删除数据的日志行数

    在这里插入图片描述

    在这里插入图片描述

    1. 确定日志名称和路径为:e:\journal\journal\20221025.001
    2. 确定日志被删除的数据行(偏移量)起始:11396584,结束:11404352
    3. 打开日志文件。也可以默认参数为当前使用的日志文件。
    /// w ##class(M.M110).RestoreData("e:\journal\journal\20221026.001 ")
    ClassMethod RestoreData(filepath As %String = {##class(%SYS.Journal.System).GetCurrentFileName()})
    {
    	#dim obj as %SYS.Journal.File = ##class(%SYS.Journal.File).%OpenId(filepath)
    	zw obj
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    1. 找到对应日志的偏移量。
    	# dim mSetKillRecordObj as %SYS.Journal.SetKillRecord
    	
    	#; 为空则从头开始找
    	s:(offset = 0) mSetKillRecordObj = obj.FirstRecord
    	
    	#; 不为0,找到对应的偏移量
    	s:(offset > 0) mSetKillRecordObj = obj.GetRecordAt(offset)
    	
    	#; 如果输入的Offset不存在,则寻找下一条记录
    	s:(mSetKillRecordObj = "") mSetKillRecordObj = obj.GetRecordAfter(offset) 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. 根据表明找到对应的主数据Global
    /// w ##class(M.Journal).GetDataLocation("M.T.Person")
    ClassMethod GetDataLocation(classname)
    {
    	&sql(SELECT DataLocation into :masterGlobal FROM  %Dictionary.CompiledStorage WHERE parent = :classname )
    	q masterGlobal
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    USER>w ##class(M.M110).GetDataLocation("M.T.Person")
    ^M.T.PersonD
    
    • 1
    • 2
    1. 开始从起始偏移量循环到结束偏移量或日志末位。
    	while($isobject(mSetKillRecordObj) = 1)
    	{	
    		#; 说明已经是最后一条,退出循环
    		q:($g(mSetKillRecordObj) = "") 	
    		
    		if ((end '= "")&&(mSetKillRecordObj.Address > end)) {
    			q
    		}
    		
    		s mSetKillRecordObj = obj.GetRecordAfter(mSetKillRecordObj.Address)	
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    1. 因为是删表数据,在循环中判断操作类型为KILL的,其他都过滤掉。
    		#; 过滤Begintrans\EndTrans\SET,只留下KILL
    		if ((mSetKillRecordObj.TypeName '= "KILL"))  
    		{
    			#;跳到下一条记录
    			s mSetKillRecordObj = obj.GetRecordAfter(mSetKillRecordObj.Address)
    			continue
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 过滤掉其他非目标GLOBAL
    		#; 过滤非目标Global
    		if (mSetKillRecordObj.GlobalNode '[ masterGlobal)  
    		{
    			#; 跳到下一条记录
    			s mSetKillRecordObj = obj.GetRecordAfter(mSetKillRecordObj.Address)
    			continue
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 取到每条日志对象的数据节点,新值,旧值。
    		#; 取GlobalNode节点	
    		s globalNode = mSetKillRecordObj.GlobalNode	
    		
    		#; 取新值,删除的话为空
    		s newValue = mSetKillRecordObj.NewValue
    		
    		#; 取旧值,需要回复的数据
    		s oldValue = mSetKillRecordObj.OldValue
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. Global下标节点,也就是ID,并重新给ID赋值。

    注:如果是生产数据,ID对应是很重要的,否则其他关联表会找不到对应的外键ID数据。

    		#; 取Global下标节点,也就是ID
    		s id = $qsubscript(globalNode, 1)
    		
    		#; 给ID赋值为0,否则ID顺序自增
    		s @masterGlobal = (id - 1)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 组装SQL Insert语句进行插入即可。
    		#; 组装数据
    		s mPerson = {}
    		s mPerson.name = $lg(oldValue, 2)
    		s mPerson.age = $lg(oldValue, 3)
    		s mPerson.no = $lg(oldValue, 4)
    		
    		#; 添加数据
    		s result = ##class(IMP.Common.Sql).Save(mPerson, tableName)
    		w "id为:" _ result,!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    执行结果为:

    USER> w ##class(M.M110).RestoreData("e:\journal\journal\20221025.001", "M_T.Person", 11396584 , 11404352)
    id为:1
    id为:2
    id为:3
    id为:4
    id为:5
    ...
    id为:97
    id为:98
    id为:99
    id为:100
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

    在这里插入图片描述

    有4条数据有特殊符号插入失败。这里就不多做研究了。

    在这里插入图片描述

    完整代码

    /// author:姚鑫
    /// w ##class(M.T.Person).MapField().%ToJSON()
    /// w ##class(M.T.Person).Populate(100)
    Class M.T.Person Extends (%Persistent, IMP.Common.Map, %Populate) [ ClassType = persistent, SqlRowIdName = id, SqlTableName = Person ]
    {
    
    Property MTName As %String(POPSPEC = "Name()") [ Aliases = {name}, SqlFieldName = MT_Name ];
    
    Property MTAge As %Integer(POPSPEC = "Integer(18,30)") [ Aliases = {age}, SqlFieldName = MT_Age ];
    
    Property MTNo As %String(POPSPEC = "Integer(100000,999999)") [ Aliases = {no}, SqlFieldName = MT_No ];
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    /// author:姚鑫
    Class M.M110 Extends %RegisteredObject
    {
    
    /// w ##class(M.M110).RestoreData("e:\journal\journal\20221025.001", "M_T.Person", 11396584 , 11404352)
    ClassMethod RestoreData(filepath As %String = {##class(%SYS.Journal.System).GetCurrentFileName()}, tableName As %String, offset As %Integer = 0, end As %Integer = 100000) As %Status
    {
    	s obj = ##class(%SYS.Journal.File).%OpenId(filepath)
    	q:('$IsObject(obj)) "打开journal文件 "_filepath_"  失败,请检查日志文件名是否正确"
    	
    	# dim mSetKillRecordObj as %SYS.Journal.SetKillRecord
    	#; 为空则从头开始找
    	s:(offset = 0) mSetKillRecordObj = obj.FirstRecord
    	s:(offset > 0) mSetKillRecordObj = obj.GetRecordAt(offset)
    	
    	#; 如果输入的Offset不存在,则寻找下一条记录
    	s:(mSetKillRecordObj = "") mSetKillRecordObj = obj.GetRecordAfter(offset) 
    	
    	s className = $replace(tableName , "_", ".")
    	s masterGlobal = ..GetDataLocation(className)
    	
    	s currentAddress = offset
    	
    	while($isobject(mSetKillRecordObj) = 1)
    	{	
    		#; 说明已经是最后一条,退出循环
    		q:($g(mSetKillRecordObj) = "") 	
    		
    		#; 过滤Begintrans\EndTrans\SET,只留下KILL
    		if ((mSetKillRecordObj.TypeName '= "KILL"))  
    		{
    			#;跳到下一条记录
    			s mSetKillRecordObj = obj.GetRecordAfter(mSetKillRecordObj.Address)
    			continue
    		}
    		
    		#; 过滤非目标Global
    		if (mSetKillRecordObj.GlobalNode '[ masterGlobal)  
    		{
    			#; 跳到下一条记录
    			s mSetKillRecordObj = obj.GetRecordAfter(mSetKillRecordObj.Address)
    			continue
    		}
    	
    		if ((end '= "")&&(mSetKillRecordObj.Address > end)) {
    			q
    		}
    		
    		#;GlobalNode节点	
    		s globalNode = mSetKillRecordObj.GlobalNode	
    		
    		#; 取新值,删除的话为空
    		s newValue = mSetKillRecordObj.NewValue
    		
    		#; 取旧值,需要回复的数据
    		s oldValue = mSetKillRecordObj.OldValue
    		
    		#;Global下标节点,也就是ID
    		s id = $qsubscript(globalNode, 1)
    		
    		#; 给ID赋值,否则ID顺序自增
    		s @masterGlobal = (id - 1)
    		
    		#; 组装数据
    		s mPerson = {}
    		s mPerson.name = $lg(oldValue, 2)
    		s mPerson.age = $lg(oldValue, 3)
    		s mPerson.no = $lg(oldValue, 4)
    		
    		#; 添加数据,自行改为Insert语句
    		s result = ##class(IMP.Common.Sql).Save(mPerson, tableName)
    		w "id为:" _ result,!
    		s mSetKillRecordObj = obj.GetRecordAfter(mSetKillRecordObj.Address)	
    		
    	}
    	q $$$OK
    }
    
    /// w ##class(M.M110).GetDataLocation("M.T.Person")
    ClassMethod GetDataLocation(classname)
    {
    	&sql(SELECT DataLocation into :masterGlobal FROM  %Dictionary.CompiledStorage WHERE parent = :classname )
    	q masterGlobal
    }
    
    /// w ##class(M.M110).DeleteTable("M_T.Person")
    ClassMethod DeleteTable(classname)
    {
    	&sql(Delete M_T.Person )
    	q SQLCODE
    }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
  • 相关阅读:
    nginx异常重启
    【ES6知识】async 函数与代码优雅写法
    16.在一行中懒加载图片
    ecology8恢复被废弃的应用和模块
    Python 编程基础 | 第四章-函数 | 4.1、函数定义
    求函数f(x,y)在曲线C上的最大方向导数问题
    性能调优必备神器-Jprofiler解析
    kubelet gc 源码分析
    rdkit&molhash | 分子哈希算法
    【Markdown】图片缩放
  • 原文地址:https://blog.csdn.net/yaoxin521123/article/details/127538054