下面直接上代码(copy到你的数据库里面直接就可以运行):

  

CREATE PROCEDURE [dbo].[SP_UPDATE_LOG]
@TABLENAME VARCHAR(50)
AS
BEGIN
SET NOCOUNT ON;
IF NOT EXISTS(SELECT * FROM sys.tables WHERE NAME = @TABLENAME AND TYPE = 'U' )
BEGIN
PRINT'ERROR:not exist table '+@TABLENAME
RETURN
END
IF (@TABLENAME LIKE'BACKUP_%' OR @TABLENAME='UPDATE_LOG' )
BEGIN
--PRINT'ERROR:not exist table '+@TABLENAME
RETURN
END
--================================判断是否存在 UPDATE_LOG 表============================
IF NOT EXISTS(SELECT * FROM sys.tables WHERE NAME = 'UPDATE_LOG' AND TYPE = 'U')
CREATE TABLE UPDATE_LOG
(
UpdateGUID VARCHAR(36),
UpdateTime DATETIME,
TableName varchar(20),
UpdateType varchar(6),
RollBackSQL varchar(MAX),
ExecSQL VARCHAR(500)
)
--=================================判断是否存在 BACKUP_ 表================================
IF NOT EXISTS(SELECT * FROM sys.tables WHERE NAME = 'BACKUP_'+@TABLENAME AND TYPE = 'U')
BEGIN
DECLARE test_Cursor CURSOR FOR
SELECT COLUMN_NAME,DATA_TYPE,CHARACTER_MAXIMUM_LENGTH FROM INFORMATION_SCHEMA.columns
WHERE TABLE_NAME=@TABLENAME
OPEN test_Cursor
DECLARE @SQLTB NVARCHAR(MAX)=''
DECLARE @COLUMN_NAME NVARCHAR(50),@DATA_TYPE VARCHAR(20),@CHARACTER_MAXIMUM_LENGTH INT
FETCH NEXT FROM test_Cursor INTO @COLUMN_NAME,@DATA_TYPE,@CHARACTER_MAXIMUM_LENGTH
WHILE @@FETCH_STATUS=0
BEGIN
SET @SQLTB=@SQLTB+'['+@COLUMN_NAME+'] '+@DATA_TYPE+CASE ISNULL(@CHARACTER_MAXIMUM_LENGTH,0) WHEN 0 THEN '' WHEN -1 THEN '(MAX)' ELSE'('+CAST(@CHARACTER_MAXIMUM_LENGTH AS VARCHAR(10))+')' END+','
FETCH NEXT FROM test_Cursor INTO @COLUMN_NAME,@DATA_TYPE,@CHARACTER_MAXIMUM_LENGTH
END
SET @SQLTB='CREATE TABLE BACKUP_'+@TABLENAME+' (UpdateGUID varchar(36),UpdateType Varchar(10),'+SUBSTRING(@SQLTB,1,LEN(@SQLTB)-1)+')'
EXEC (@SQLTB)
CLOSE test_Cursor
DEALLOCATE test_Cursor
END
--======================================判断是否存在 UPDATE 触发器=========================
IF NOT EXISTS(SELECT * FROM sys.objects WHERE NAME = 'tg_'+@TABLENAME+'_Update' AND TYPE = 'TR')
BEGIN
DECLARE @SQLTR NVARCHAR(MAX)
SET @SQLTR='
CREATE TRIGGER tg_'+@TABLENAME+'_Update
ON '+@TABLENAME+'
AFTER Update,Delete,Insert
AS
BEGIN
SET NOCOUNT ON;
--==============================获取GUID==========================================
DECLARE @NEWID VARCHAR(36)=NEWID() --===========================将删掉或新增的数据插入备份表=========================
DECLARE @ROWCOUNT INT
INSERT INTO [dbo].[BACKUP_'+@TABLENAME+']
SELECT @NEWID,''DELETE'',* FROM deleted
SET @ROWCOUNT=@@ROWCOUNT
IF @ROWCOUNT>0
BEGIN
INSERT INTO [dbo].[BACKUP_'+@TABLENAME+']
SELECT @NEWID,''INSERT'',* FROM inserted
END
ELSE
BEGIN
INSERT INTO [dbo].[BACKUP_'+@TABLENAME+']
SELECT @NEWID,''INSERT'',* FROM inserted
SET @ROWCOUNT=@@ROWCOUNT
END --==============================记录日志和回滚操作的SQL=========================== --******************生成插入语句用到的列名(需避开自增字段)********************
DECLARE @COLUMN1 NVARCHAR(MAX)=''''
SELECT @COLUMN1+='',[''+COLUMN_NAME+'']'' FROM INFORMATION_SCHEMA.columns
WHERE TABLE_NAME='''+@TABLENAME+'''
AND COLUMNPROPERTY(OBJECT_ID('''+@TABLENAME+'''),COLUMN_NAME,''IsIdentity'')<>1 --非自增字段
SET @COLUMN1=SUBSTRING(@COLUMN1,2,LEN(@COLUMN1)) --*******************动态定义变量、删除条件匹配的列********************
DECLARE @DECLARE VARCHAR(MAX)='''',@INTODECLARE VARCHAR(MAX)='''',@WHERE VARCHAR(MAX)='''',@COLUMN2 VARCHAR(MAX)=''''
SELECT @DECLARE+=''@''+COLUMN_NAME+'' ''+DATA_TYPE+CASE ISNULL(CAST(CHARACTER_OCTET_LENGTH AS VARCHAR(10)),'''') WHEN '''' THEN '','' WHEN ''-1'' THEN ''(MAX),'' ELSE ''(''+CAST(CHARACTER_OCTET_LENGTH AS VARCHAR(10))+''),'' END,
@INTODECLARE+=''@''+COLUMN_NAME+'','',
@COLUMN2+=''[''+COLUMN_NAME+''],'' ,
@WHERE += ''ISNULL(''+ COLUMN_NAME+'','''''''')=ISNULL(@''+COLUMN_NAME+'','''''''') AND ''
FROM INFORMATION_SCHEMA.columns
WHERE TABLE_NAME='''+@TABLENAME+'''
SET @DECLARE=LEFT(@DECLARE,LEN(@DECLARE)-1)
SET @INTODECLARE=LEFT(@INTODECLARE,LEN(@INTODECLARE)-1)
SET @COLUMN2=LEFT(@COLUMN2,LEN(@COLUMN2)-1)
SET @WHERE= LEFT(@WHERE,LEN(@WHERE)-3) --*******************判断是否还原当前表的最近一次操作*******************
DECLARE @SQL_ISLAST VARCHAR(MAX)=''
SET NOCOUNT ON
DECLARE @maxdate datetime
SELECT @maxdate=max(updatetime) FROM UPDATE_LOG WHERE TableName='''''+@TABLENAME+'''''
IF NOT EXISTS(SELECT 1 FROM UPDATE_LOG WHERE UpdateTime=@maxdate AND UPDATEGUID=''''''+@NEWID+'''''')
BEGIN
DECLARE @MAXGUID VARCHAR(50)
SELECT @MAXGUID=UPDATEGUID FROM UPDATE_LOG WHERE UpdateTime=@maxdate
PRINT ''''此操作并非最近一次操作,请逐步还原,此表最近一次操作的GUID是:''''+@MAXGUID
RETURN
END
'' --********************还原insert和update操作用到的SQL******************* DECLARE @SQL_DELETE VARCHAR(MAX)=''
SET ROWCOUNT 1 --设定相同条件下只删除1行
DECLARE Cursor_ CURSOR FOR
SELECT ''+@COLUMN2+'' FROM BACKUP_'+@TABLENAME+' WHERE UPDATEGUID= ''''''+@NEWID+'''''' AND UpdateType=''''INSERT''''
OPEN Cursor_
DECLARE ''+@DECLARE+''
FETCH NEXT FROM Cursor_ INTO ''+@INTODECLARE+''
WHILE @@FETCH_STATUS=0
BEGIN
DELETE FROM '+@TABLENAME+' WHERE ''+@WHERE+''
FETCH NEXT FROM Cursor_ INTO ''+@INTODECLARE+''
END
CLOSE Cursor_
DEALLOCATE Cursor_
SET ROWCOUNT 0
'' --*********************还原delete和update操作用到的SQL******************* DECLARE @SQL_INSERT VARCHAR(MAX)=''
INSERT INTO '+@TABLENAME+' SELECT ''+@COLUMN1+'' FROM BACKUP_'+@TABLENAME+' WHERE UPDATEGUID=''''''+@NEWID+'''''' AND UpdateType=''''DELETE''''
'' --*********************还原操作之后把备份表和log表的记录删掉************* DECLARE @SQL_DELGUID VARCHAR(MAX)=''
DELETE FROM BACKUP_'+@TABLENAME+' WHERE UPDATEGUID IN(SELECT UPDATEGUID FROM UPDATE_LOG WHERE UpdateTime>=@maxdate AND TableName='''''+@TABLENAME+''''')
DELETE FROM UPDATE_LOG WHERE UpdateTime>=@maxdate AND TableName='''''+@TABLENAME+'''''
PRINT ''''回滚操作执行成功,共恢复 ''+CAST(@ROWCOUNT AS VARCHAR(10))+'' 条记录''''
SET NOCOUNT OFF
'' --*********************执行还原操作的SQL********************************** DECLARE @EXECSQL VARCHAR(500)=''
DECLARE @SQL VARCHAR(MAX)
SELECT @SQL=ROLLBACKSQL FROM UPDATE_LOG WHERE UPDATEGUID=''''''+@NEWID+''''''
EXEC(@SQL)
'' --==============================判断执行的哪种操作方式================================= DECLARE @DoType VARCHAR(MAX)=''UPDATE''
IF NOT EXISTS(SELECT 1 FROM deleted)
SET @DoType=''INSERT''
IF NOT EXISTS(SELECT 1 FROM inserted)
SET @DoType=''DELETE''
IF NOT EXISTS(SELECT 1 FROM deleted) AND NOT EXISTS(SELECT 1 FROM inserted)
RETURN
IF @DoType=''UPDATE''
BEGIN
INSERT INTO [dbo].[UPDATE_LOG]
SELECT @NEWID,GETDATE(),'''+@TABLENAME+''',''UPDATE'',@SQL_ISLAST+@SQL_DELETE+@SQL_INSERT+@SQL_DELGUID,@EXECSQL
RETURN
END
IF @DoType=''DELETE''
BEGIN
INSERT INTO [dbo].[UPDATE_LOG]
SELECT @NEWID,GETDATE(),'''+@TABLENAME+''',''DELETE'',@SQL_ISLAST+@SQL_INSERT+@SQL_DELGUID,@EXECSQL
RETURN
END
IF @DoType=''INSERT''
BEGIN
INSERT INTO [dbo].[UPDATE_LOG]
SELECT @NEWID,GETDATE(),'''+@TABLENAME+''',''INSERT'',@SQL_ISLAST+@SQL_DELETE+@SQL_DELGUID,@EXECSQL
RETURN
END
END
'
EXEC (@SQLTR)
END
END

运行这段代码,你会创建一个存储过程,下面来建一个测试表简单测一下这个存储过程的功能吧:

CREATE TABLE test(
[id] [int] NULL,
[name] [varchar](10) NULL
) INSERT INTO test
SELECT 1,'a'
UNION ALL
SELECT 2,'b'
UNION ALL
SELECT 3,'c'
UNION ALL
SELECT 4,'d'
UNION ALL
SELECT 5,'a'
UNION ALL
SELECT 6,'b' SELECT * FROM test

检查一下,表建好了:

接着执行存储过程给test表添加回滚日志:

EXEC SP_UPDATE_LOG 'test'    --给test表建立update回滚日志
SELECT * FROM [dbo].[BACKUP_test] --test表数据备份
SELECT * FROM [dbo].[UPDATE_LOG] --update操作记录

这时候你会发现生成了两张表:backup_test 和 update_log,包括test表下建立了触发器,backup_test是test表的备份表,由test表专用,update_log表是所有建立update回滚日志的表所公用的。这个表里面记录每张表操作的时间,做了何种操作,包括执行回滚的SQL:

下面测一下回滚的功能吧,我要把test表改得面目全非,然后再执行回滚:

update 操作被我一不小心执行了两次,变成了这副德行,下面我开始还原操作,先查询下update_log这张表:

产生了两条操作记录,copy ExecSQL里面的SQL语句执行,注意要先执行时间最近的操作记录,一步一步还原:

还原一步之后变成了这个样子,下面再还原一步:

到目前为止看下完全还原了吧~

好了,这个update的回滚日志也该介绍完了~终于打完了,手好累啊~~

PS:执行delete和insert操作也是一样的回滚步骤

原文:https://blog.csdn.net/Wikey_Zhang/article/details/73994020

[转]SQLServer添加UPDATE回滚日志(update/delete/insert)的更多相关文章

  1. MySQL中的重做日志(redo log),回滚日志(undo log),以及二进制日志(binlog)的简单总结

    MySQL中有六种日志文件,分别是:重做日志(redo log).回滚日志(undo log).二进制日志(binlog).错误日志(errorlog).慢查询日志(slow query log).一 ...

  2. python logging 实现的进程安全的文件回滚日志类

    python标准库中的logging模块在记录日志时经常会用到,但在实际使用发现它自带的用于本地日志回滚的类 logging.handlers.RotatingFileHandler 在多进程环境下会 ...

  3. mysql回滚日志

    一.回滚日志(undo log) 1.作用 保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读 2.内容 逻辑格式的日志,在执行undo的时候 ...

  4. Log4net入门(回滚日志文件篇)

    在上一篇Log4net(日志文件篇)中,我们使用"log4net.Appender.FileAppender"将日志信息输出到一个单一的文件中,随着应用程序的持续使用,该日志文件会 ...

  5. sqlserver事务与回滚

    如果要在Production执行数据改动必须小心,可以使用事务提前验证一下自己写的SQL是不是你期望的.尤其是Update的where 条件有问题的话,跟新的记录就会超出预期的范围.如下面的语句,一着 ...

  6. sprint test 添加事务回滚机制

    1.原因: 单元测试的时候频繁操作数据库需要修改很多数据,造成不必要的操作,添加事务之后就可以重复对一条数据进行操作,并且在返回结果后进行回滚. 2.解决: 原先继承的是  AbstractJUnit ...

  7. Quartz:Quartz添加事务回滚报错

    自动任务类: @PersistJobDataAfterExecution @DisallowConcurrentExecution public class ReCodeBack implements ...

  8. 两表关联更新,用于update 回滚

    create table test1 as select * from dba_objects; create table test2 as select * from dba_objects; cr ...

  9. SQLite中的WAL机制详细介绍-与回滚日志原理

    一.什么是WAL? WAL的全称是Write Ahead Logging,它是很多数据库中用于实现原子事务的一种机制,SQLite在3.7.0版本引入了该特性. 二.WAL如何工作? 在引入WAL机制 ...

随机推荐

  1. QT画图

    if (0) { QApplication a(argv, args); QGraphicsScene scene; scene.setSceneRect(-300,-300,600,600); sc ...

  2. RIP 知识要点

    RIP知识要点: UDP:520 版本:v1(广播包更新) / v2(组播更新 224.0.0.9  ) 度量值:跳数(最多跳15跳,路由为16跳时路由不可达) =================== ...

  3. 11.求二元查找树的镜像[MirrorOfBST]

    [题目] 输入一颗二元查找树,将该树转换为它的镜像,即在转换后的二元查找树中,左子树的结点都大于右子树的结点.用递归和循环两种方法完成树的镜像转换. 例如输入: 8    /  \  6      1 ...

  4. InpOut32 InputTest.cpp hacking

    /************************************************************************************ * InpOut32 Inp ...

  5. 关于15桥梁课程1&2的笔记以及待做事项的梳理

    1.指针所占用的空间是固定的 2.void *malloc(sizeof(int)); (这玩意耗时间,老师说通过内存池解决) free(p);free(p);   两次free()报错,正确的做法: ...

  6. 【前端】HTML入门笔记

    教程 HTML 指的是超文本标记语言 (Hyper Text Markup Language).使用标记标签来描述网页 HTML 提示:使用小写标签\属性\属性参考手册\HTML颜色\HTML颜色名 ...

  7. bzoj 4545 DQS 的 Trie

    老年选手不会 SAM 也不会 LCT 系列 我的数据结构好菜啊 qnq 一颗 Trie 树,$q$ 次询问,每次可以是: 1.求这棵树上本质不同的子串数量 2.插入一个子树,保证总大小不超过 $100 ...

  8. is is not == !=之间的区别

    简单来说,python中的is与is not运算符可判断两个对象是否为同一对象.若为同一个对象,则对象1 is 对象2为True .反正,若非同一个对象,则对象1 is not 对象2为True 要理 ...

  9. 在Toad中导入导出数据

    一.导出数据 右键点击所要导出的表名,选择“Export   Date”     二.导入数据   一.右键点击表名,选择“import  date” 二.下一步 三.下一步 四.下一步 在oracl ...

  10. strtotime出现时区问题不一致的解决方法

    学习源头:https://blog.csdn.net/longjuanfengzc/article/details/80622842 https://segmentfault.com/q/101000 ...