SQLServer添加UPDATE回滾日誌(update/delete/insert)

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 test set name = case when id in (1,3,5) then 'Hello' else 'World' END,ID = 999
update test set name = case when id in (1,3,5) then 'Hello' else 'World' END,ID = 999

update 操作被我一不小心執行了兩次,變成了這副德行,下面我開始還原操作,先查詢下update_log這張表:

產生了兩條操作記錄,copy ExecSQL裏面的SQL語句執行,注意要先執行時間最近的操作記錄,一步一步還原:

DECLARE @SQL VARCHAR(MAX)
SELECT @SQL = ROLLBACKSQL FROM UPDATE_LOG WHERE UPDATEGUID = '88888'
EXEC(@SQL)
SELECT * FROM TEST

 

 

還原一步之後變成了這個樣子,下面再還原一步:

DECLARE @SQL VARCHAR(MAX)
SELECT @SQL = ROLLBACKSQL FROM UPDATE_LOG WHERE UPDATEGUID = '88888'
EXEC(@SQL)
SELECT * FROM TEST ORDER BY ID

到目前爲止看下完全還原了吧~


原文鏈接:https://blog.csdn.net/wikey_zhang/article/details/73994020

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章