SQL 触发器内游标中的服务器错误处理

SQL Server error handling in cursor inside trigger

我是SQL服务器错误处理的新手,我的英语不是很清楚,所以如果有任何误解,我提前道歉。

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

我用 3 tables 做了一个测试数据库:

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 中。

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
The transaction ended in the trigger. The batch has been aborted.

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

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

  1. tB中所有插入的记录
  2. tC的所有好记录
  3. tErrorLog 中记录了错误

谢谢!

您的触发器中有两场重大灾难

  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。只是不要.....

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

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

1 是 "Record processed OK",2 是 "Record processing fault"。

我用 WHERE Status = 0 填充光标。

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

我没有工作,所以我 运行 来自 Windows 调度程序的 SP,带有批处理文件和 SQLCMD 命令。

现在处理的很好,而且第一次很好用。感谢您的帮助!