SQLServer 自定义回滚表(update/delete/insert)并实行回滚
程序员文章站
2024-03-16 22:08:22
...
实现update操作的回滚日志的建立。首先要注意的是:这里的update并不只是表的update操作,它包含delete和insert操作!
下面直接上代码(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 ’aaa@qq.com
- RETURN
- END
- IF (@TABLENAME LIKE‘BACKUP_%’ OR @TABLENAME=‘UPDATE_LOG’ )
- BEGIN
- –PRINT’ERROR:not exist table ’aaa@qq.com
- 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_’aaa@qq.com AND TYPE = ‘U’)
- BEGIN
- DECLARE test_Cursor CURSOR FOR
- SELECT COLUMN_NAME,DATA_TYPE,CHARACTER_MAXIMUM_LENGTH FROM INFORMATION_SCHEMA.columns
- WHERE aaa@qq.com
- 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 @aaa@qq.com+‘[‘aaa@qq.com_NAME+‘] ’aaa@qq.com_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_’aaa@qq.com+‘ (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_’aaa@qq.com+‘_Update’ AND TYPE = ‘TR’)
- BEGIN
- DECLARE @SQLTR NVARCHAR(MAX)
- SET @SQLTR=’
- CREATE TRIGGER tg_‘aaa@qq.com+’_Update
- ON ‘aaa@qq.com+’
- AFTER Update,Delete,Insert
- AS
- BEGIN
- SET NOCOUNT ON;
- –==============================获取GUID==========================================
- DECLARE @NEWID VARCHAR(36)=NEWID()
- –===========================将删掉或新增的数据插入备份表=========================
- DECLARE @ROWCOUNT INT
- INSERT INTO [dbo].[BACKUP_‘aaa@qq.com+’]
- SELECT @NEWID,”DELETE”,* FROM deleted
- SET @aaa@qq.com@ROWCOUNT
- IF @ROWCOUNT>0
- BEGIN
- INSERT INTO [dbo].[BACKUP_‘aaa@qq.com+’]
- SELECT @NEWID,”INSERT”,* FROM inserted
- END
- ELSE
- BEGIN
- INSERT INTO [dbo].[BACKUP_‘aaa@qq.com+’]
- SELECT @NEWID,”INSERT”,* FROM inserted
- SET @aaa@qq.com@ROWCOUNT
- END
- –==============================记录日志和回滚操作的SQL===========================
- –******************生成插入语句用到的列名(需避开自增字段)********************
- DECLARE @COLUMN1 NVARCHAR(MAX)=””
- SELECT @COLUMN1+=”,[”+COLUMN_NAME+”]” FROM INFORMATION_SCHEMA.columns
- WHERE TABLE_NAME=”‘aaa@qq.com+’”
- AND COLUMNPROPERTY(OBJECT_ID(”‘aaa@qq.com+’”),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=”‘aaa@qq.com+’”
- 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=””‘aaa@qq.com+’””
- IF NOT EXISTS(SELECT 1 FROM UPDATE_LOG WHERE aaa@qq.com AND UPDATEGUID=”””aaa@qq.com+”””)
- BEGIN
- DECLARE @MAXGUID VARCHAR(50)
- SELECT @MAXGUID=UPDATEGUID FROM UPDATE_LOG WHERE aaa@qq.com
- PRINT ””此操作并非最近一次操作,请逐步还原,此表最近一次操作的GUID是:””aaa@qq.com
- RETURN
- END
- ”
- –********************还原insert和update操作用到的SQL*******************
- DECLARE @SQL_DELETE VARCHAR(MAX)=”
- SET ROWCOUNT 1 –设定相同条件下只删除1行
- DECLARE Cursor_ CURSOR FOR
- SELECT ”aaa@qq.com+” FROM BACKUP_‘aaa@qq.com+’ WHERE UPDATEGUID= ”””aaa@qq.com+””” AND UpdateType=””INSERT””
- OPEN Cursor_
- DECLARE ”aaa@qq.comDECLARE+”
- FETCH NEXT FROM Cursor_ INTO ”aaa@qq.com+”
- WHILE @@FETCH_STATUS=0
- BEGIN
- DELETE FROM ‘aaa@qq.com+’ WHERE ”aaa@qq.comWHERE+”
- FETCH NEXT FROM Cursor_ INTO ”aaa@qq.com+”
- END
- CLOSE Cursor_
- DEALLOCATE Cursor_
- SET ROWCOUNT 0
- ”
- –*********************还原delete和update操作用到的SQL*******************
- DECLARE @SQL_INSERT VARCHAR(MAX)=”
- INSERT INTO ‘aaa@qq.com+’ SELECT ”aaa@qq.com+” FROM BACKUP_‘aaa@qq.com+’ WHERE UPDATEGUID=”””aaa@qq.com+””” AND UpdateType=””DELETE””
- ”
- –*********************还原操作之后把备份表和log表的记录删掉*************
- DECLARE @SQL_DELGUID VARCHAR(MAX)=”
- DELETE FROM BACKUP_‘aaa@qq.com+’ WHERE UPDATEGUID IN(SELECT UPDATEGUID FROM UPDATE_LOG WHERE UpdateTime>aaa@qq.com AND TableName=””‘aaa@qq.com+’””)
- DELETE FROM UPDATE_LOG WHERE UpdateTime>aaa@qq.com AND TableName=””‘aaa@qq.com+’””
- 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=”””aaa@qq.com+”””
- 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(),”‘aaa@qq.com+’”,”UPDATE”,@aaa@qq.comaaa@qq.comaaa@qq.com_DELGUID,@EXECSQL
- RETURN
- END
- IF @DoType=”DELETE”
- BEGIN
- INSERT INTO [dbo].[UPDATE_LOG]
- SELECT @NEWID,GETDATE(),”‘aaa@qq.com+’”,”DELETE”,@aaa@qq.comaaa@qq.com_DELGUID,@EXECSQL
- RETURN
- END
- IF @DoType=”INSERT”
- BEGIN
- INSERT INTO [dbo].[UPDATE_LOG]
- SELECT @NEWID,GETDATE(),”‘aaa@qq.com+’”,”INSERT”,@aaa@qq.comaaa@qq.com_DELGUID,@EXECSQL
- RETURN
- END
- END
- ’
- EXEC (@SQLTR)
- END
- END
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 'aaa@qq.com
RETURN
END
IF (@TABLENAME LIKE'BACKUP_%' OR @TABLENAME='UPDATE_LOG' )
BEGIN
--PRINT'ERROR:not exist table 'aaa@qq.com
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_'aaa@qq.com AND TYPE = 'U')
BEGIN
DECLARE test_Cursor CURSOR FOR
SELECT COLUMN_NAME,DATA_TYPE,CHARACTER_MAXIMUM_LENGTH FROM INFORMATION_SCHEMA.columns
WHERE aaa@qq.com
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 @aaa@qq.com+'['aaa@qq.com_NAME+'] 'aaa@qq.com_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_'aaa@qq.com+' (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_'aaa@qq.com+'_Update' AND TYPE = 'TR')
BEGIN
DECLARE @SQLTR NVARCHAR(MAX)
SET @SQLTR='
CREATE TRIGGER tg_'aaa@qq.com+'_Update
ON 'aaa@qq.com+'
AFTER Update,Delete,Insert
AS
BEGIN
SET NOCOUNT ON;
--==============================获取GUID==========================================
DECLARE @NEWID VARCHAR(36)=NEWID()
--===========================将删掉或新增的数据插入备份表=========================
DECLARE @ROWCOUNT INT
INSERT INTO [dbo].[BACKUP_'aaa@qq.com+']
SELECT @NEWID,''DELETE'',* FROM deleted
SET @aaa@qq.com@ROWCOUNT
IF @ROWCOUNT>0
BEGIN
INSERT INTO [dbo].[BACKUP_'aaa@qq.com+']
SELECT @NEWID,''INSERT'',* FROM inserted
END
ELSE
BEGIN
INSERT INTO [dbo].[BACKUP_'aaa@qq.com+']
SELECT @NEWID,''INSERT'',* FROM inserted
SET @aaa@qq.com@ROWCOUNT
END
--==============================记录日志和回滚操作的SQL===========================
--******************生成插入语句用到的列名(需避开自增字段)********************
DECLARE @COLUMN1 NVARCHAR(MAX)=''''
SELECT @COLUMN1+='',[''+COLUMN_NAME+'']'' FROM INFORMATION_SCHEMA.columns
WHERE TABLE_NAME='''aaa@qq.com+'''
AND COLUMNPROPERTY(OBJECT_ID('''aaa@qq.com+'''),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='''aaa@qq.com+'''
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='''''aaa@qq.com+'''''
IF NOT EXISTS(SELECT 1 FROM UPDATE_LOG WHERE aaa@qq.com AND UPDATEGUID=''''''aaa@qq.com+'''''')
BEGIN
DECLARE @MAXGUID VARCHAR(50)
SELECT @MAXGUID=UPDATEGUID FROM UPDATE_LOG WHERE aaa@qq.com
PRINT ''''此操作并非最近一次操作,请逐步还原,此表最近一次操作的GUID是:''''aaa@qq.com
RETURN
END
''
--********************还原insert和update操作用到的SQL*******************
DECLARE @SQL_DELETE VARCHAR(MAX)=''
SET ROWCOUNT 1 --设定相同条件下只删除1行
DECLARE Cursor_ CURSOR FOR
SELECT ''aaa@qq.com+'' FROM BACKUP_'aaa@qq.com+' WHERE UPDATEGUID= ''''''aaa@qq.com+'''''' AND UpdateType=''''INSERT''''
OPEN Cursor_
DECLARE ''aaa@qq.com+''
FETCH NEXT FROM Cursor_ INTO ''aaa@qq.com+''
WHILE @@FETCH_STATUS=0
BEGIN
DELETE FROM 'aaa@qq.com+' WHERE ''aaa@qq.com+''
FETCH NEXT FROM Cursor_ INTO ''aaa@qq.com+''
END
CLOSE Cursor_
DEALLOCATE Cursor_
SET ROWCOUNT 0
''
--*********************还原delete和update操作用到的SQL*******************
DECLARE @SQL_INSERT VARCHAR(MAX)=''
INSERT INTO 'aaa@qq.com+' SELECT ''aaa@qq.com+'' FROM BACKUP_'aaa@qq.com+' WHERE UPDATEGUID=''''''aaa@qq.com+'''''' AND UpdateType=''''DELETE''''
''
--*********************还原操作之后把备份表和log表的记录删掉*************
DECLARE @SQL_DELGUID VARCHAR(MAX)=''
DELETE FROM BACKUP_'aaa@qq.com+' WHERE UPDATEGUID IN(SELECT UPDATEGUID FROM UPDATE_LOG WHERE UpdateTime>aaa@qq.com AND TableName='''''aaa@qq.com+''''')
DELETE FROM UPDATE_LOG WHERE UpdateTime>aaa@qq.com AND TableName='''''aaa@qq.com+'''''
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=''''''aaa@qq.com+''''''
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(),'''aaa@qq.com+''',''UPDATE'',@aaa@qq.comaaa@qq.comaaa@qq.com_DELGUID,@EXECSQL
RETURN
END
IF @DoType=''DELETE''
BEGIN
INSERT INTO [dbo].[UPDATE_LOG]
SELECT @NEWID,GETDATE(),'''aaa@qq.com+''',''DELETE'',@aaa@qq.comaaa@qq.com_DELGUID,@EXECSQL
RETURN
END
IF @DoType=''INSERT''
BEGIN
INSERT INTO [dbo].[UPDATE_LOG]
SELECT @NEWID,GETDATE(),'''aaa@qq.com+''',''INSERT'',@aaa@qq.comaaa@qq.com_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
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操作记录
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语句执行,注意要先执行时间最近的操作记录,一步一步还原:
还原一步之后变成了这个样子,下面再还原一步:
到目前为止看下完全还原了吧~
PS:执行delete和insert操作也是一样的回滚步骤