【问题标题】:SQL Server error handling in cursor inside trigger触发器内游标中的 SQL Server 错误处理
【发布时间】:2015-11-06 11:26:02
【问题描述】:

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

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 个是错误的,一切都在回滚,我得到一个错误:

消息 3609,第 16 级,状态 1,第 1 行
事务在触发器中结束。该批次已中止。

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

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

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

谢谢!

【问题讨论】:

    标签: sql-server error-handling triggers cursor


    【解决方案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。只是不要......

    【讨论】:

      【解决方案2】:

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

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

      1 为“记录处理正常”,2 为“记录处理错误”。

      我用WHERE Status = 0 填充光标。

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

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

      现在处理运行良好,而且第一次运行良好。感谢您的帮助!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-04-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-10-09
        相关资源
        最近更新 更多