游标内部触发器中的SQL Server错误处理

时间:2015-11-06 11:26:03

标签: sql-server error-handling triggers cursor

我是SQL Server错误处理的新手,我的英语不太清楚,所以我提前为任何误解道歉。

问题是:我在表中插入了多条记录。该表有一个AFTER INSERT触发器,它使用游标在FETCH WHILE循环中逐个处理记录。如果发生错误,一切都会回滚。因此,如果插入的记录中只有一个错误的字段,我就丢失了所有字段。插件也回滚,所以我找不到错误的记录。所以我需要处理游标内部的错误,只回滚错误的记录。

我用3个表创建了一个测试数据库:

TA

VarSmallint smallint
VarTinyint tinyint
String varchar(20)

TB

ID int (PK, identity)
Timestamp datetime (default: getdate())
VarSmallint smallint
VarTinyint tinyint
String varchar(20)

的tC

ID int PK
Timestamp datetime
VarTinyint1 tinyint
VarTinyint2 tinyint
String varchar(10)

tA包含3条记录,其中1条记录错误。我将此内容插入tBtB具有触发器,并将记录插入tC ony。

tC只有tinyint变量,因此插入大于255的值可能会出现问题。这是测试错误发生的地方!

我的触发器是:

ALTER TRIGGER [dbo].[trg_tB] 
ON  [dbo].[tB] 
AFTER INSERT
AS 
BEGIN
    IF @@rowcount = 0
        RETURN;

    SET NOCOUNT ON;

    DECLARE
         @ID                AS int,
         @Timestamp     AS datetime,
         @VarSmallint   AS smallint,
         @VarTinyint        AS tinyint,
         @String            AS varchar(20),

    DECLARE curNyers CURSOR DYNAMIC
    FOR
        SELECT
            [ID], [Timestamp], [VarSmallint], [VarTinyint], [String]
        FROM INSERTED
        ORDER BY [ID]

    OPEN curNyers

    FETCH NEXT FROM curNyers INTO @ID, @Timestamp, @VarSmallint, @VarTinyint, @String

    WHILE @@FETCH_STATUS = 0
    BEGIN
    BEGIN TRY
        BEGIN TRAN

        INSERT INTO [dbo].[tC]([ID], [Timestamp], [VarTinyint1], [VarTinyint2], [String])
        VALUES (@ID, @Timestamp, @VarSmallint, @VarTinyint, @String)

        COMMIT TRAN
    END TRY
    BEGIN CATCH
        ROLLBACK TRAN

        INSERT INTO [dbo].[tErrorLog]([ErrorTime], [UserName], [ErrorNumber],
                                      [ErrorSeverity], [ErrorState],
                                      [ErrorProcedure], [ErrorLine],
                                      [ErrorMessage], [RecordID])
        VALUES (SYSDATETIME(), SUSER_NAME(), ERROR_NUMBER(),
                ERROR_SEVERITY(), ERROR_STATE(),
                ERROR_PROCEDURE(), ERROR_LINE(),
                ERROR_MESSAGE(), @ID)

        END CATCH

        FETCH NEXT FROM curNyers INTO @ID, @Timestamp, @VarSmallint, @VarTinyint, @String
    END

    CLOSE curNyers
    DEALLOCATE curNyers
END

如果我插入2条带有1个错误的好记录,那么一切都在回滚,我收到了错误:

  

Msg 3609,Level 16,State 1,Line 1
  交易在触发器中结束。批次已中止。

请帮帮我!如何修改此触发器才能正常工作?

如果我插入了错误的记录,我需要:

  1. tB中所有插入的记录
  2. tC中的所有好记录
  3. 在tErrorLog中记录错误
  4. 谢谢!

2 个答案:

答案 0 :(得分:1)

您的触发器中有两个主要灾难

  1. 不要在触发器内使用光标 - 这太可怕了!当给定的操作发生时触发器触发 - 你几乎无法控制它们发射的次数和次数。因此,为了不过多地损害您的系统性能,触发器应该非常小,快速,灵活 - 执行在触发器中进行任何繁重的处理和大量处理。光标是除了之外的任何东西都是敏捷而快速的 - 它是资源占用,处理器生猪,内存泄漏的怪物 - 避免那些随时可以,而且绝对是在触发器内! (并且你不需要它们,99%的情况,无论如何)

    您可以将整个逻辑重写为一个快速,基于集合的语句:

    ALTER TRIGGER [dbo].[trg_tB] 
    ON [dbo].[tB] 
    AFTER INSERT
    AS 
    BEGIN
        INSERT INTO [dbo].[tC]([ID], [Timestamp], [VarTinyint1], [VarTinyint2], [String])
            SELECT
                [ID], [Timestamp], [VarSmallint], [VarTinyint], [String]
            FROM 
                INSERTED
    END
    
  2. 切勿在触发器内调用COMMIT TRAN。触发器在语句的上下文和事务中执行,导致它触发 - 如果一切正常,只需让触发器完成,然后事务就会正常提交。如果您需要中止,请致电ROLLBACK。但永远不会在触发器中间调用COMMIT TRAN。只是不要......

答案 1 :(得分:0)

我删除了TRIGGER,并将代码中的代码复制粘贴到STORED PROCEDURE中。

然后我向tB添加了一行Status,并将默认值设置为0.

1是"记录处理OK",2是"记录处理故障"。

我用WHERE Status = 0填充光标。

TRY部分,我将状态更新为1,在CATCH部分UPDATE将其更新为2.

我没有工作,因此我使用带有SQLCMD命令的批处理文件从Windows调度程序运行SP。

现在处理效果很好,而且它第一次运作良好。谢谢你的帮助!