我们经常会遇到因为误操作把某个表数据删除掉的情况。例如指定
Delete
语句时没有写where
条件。
发生这种情况一般都是很严重的,如果是生产数据将会造成很严重的后果。恰巧数据又是基础数据则雪上加霜。
本小节会介绍,我们误操作删表时怎么处理。如何恢复被删除的表数据。
本节只是抛砖引玉,具体完善实现细节可根据此代码个性化定制。
M.T.Person
添加100
条随机数据。USER>w ##class(M.T.Person).Populate(100)
100
USER>
/// w ##class(M.M110).DeleteTable("M_T.Person")
ClassMethod DeleteTable(tableName)
{
&sql(Delete M_T.Person )
q SQLCODE
}
USER>w ##class(M.M110).DeleteTable("M_T.Person")
0
在恢复数据之前,首先要学会如何查看日志。可以查看百讲109讲。
e:\journal\journal\20221025.001
。11396584
,结束:11404352
。/// 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
}
# 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)
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
}
USER>w ##class(M.M110).GetDataLocation("M.T.Person")
^M.T.PersonD
while($isobject(mSetKillRecordObj) = 1)
{
#; 说明已经是最后一条,退出循环
q:($g(mSetKillRecordObj) = "")
if ((end '= "")&&(mSetKillRecordObj.Address > end)) {
q
}
s mSetKillRecordObj = obj.GetRecordAfter(mSetKillRecordObj.Address)
}
KILL
的,其他都过滤掉。 #; 过滤Begintrans\EndTrans\SET,只留下KILL
if ((mSetKillRecordObj.TypeName '= "KILL"))
{
#;跳到下一条记录
s mSetKillRecordObj = obj.GetRecordAfter(mSetKillRecordObj.Address)
continue
}
GLOBAL
。 #; 过滤非目标Global
if (mSetKillRecordObj.GlobalNode '[ masterGlobal)
{
#; 跳到下一条记录
s mSetKillRecordObj = obj.GetRecordAfter(mSetKillRecordObj.Address)
continue
}
#; 取GlobalNode节点
s globalNode = mSetKillRecordObj.GlobalNode
#; 取新值,删除的话为空
s newValue = mSetKillRecordObj.NewValue
#; 取旧值,需要回复的数据
s oldValue = mSetKillRecordObj.OldValue
Global
下标节点,也就是ID
,并重新给ID
赋值。注:如果是生产数据,ID
对应是很重要的,否则其他关联表会找不到对应的外键ID
数据。
#; 取Global下标节点,也就是ID
s id = $qsubscript(globalNode, 1)
#; 给ID赋值为0,否则ID顺序自增
s @masterGlobal = (id - 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,!
执行结果为:
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
有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 ];
}
/// 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
}
}