【问题标题】:Uncommitable transaction prevents error logging in nested transaction不可提交事务防止在嵌套事务中记录错误
【发布时间】:2015-07-31 17:48:16
【问题描述】:

根据this,你可以在catch块中有一个状态,除非你先回滚,否则你不能做任何写操作。

当您尝试处理嵌套事务并进行错误记录时,这是一个问题。在下面的示例中,嵌套过程中的异常会丢失并且不会记录任何内容。

IF OBJECT_ID(N'dbo.ErrorLog', N'U') IS NOT NULL
 DROP TABLE dbo.ErrorLog;
GO

CREATE TABLE dbo.ErrorLog (Error NVARCHAR(4000));
GO

IF OBJECT_ID(N'tempdb..#Caller') IS NOT NULL
BEGIN
  DROP PROC #Caller;
END;
GO

CREATE PROCEDURE #Caller 
AS
BEGIN
  SET NOCOUNT ON;
  SET XACT_ABORT ON;

  DECLARE @transCount TINYINT = @@TRANCOUNT,
          @returnCode INT,
          @errorMessage NVARCHAR(4000),
          @errorNumber INT;

  BEGIN TRY

    IF (@transCount = 0)
    BEGIN
      BEGIN TRAN;
    END;

    EXEC @returnCode = #Called;

    IF (@returnCode <> 0)
    BEGIN 
      RAISERROR(N'Error in Called. Caller returned an error', 16, -1); 
    END;

    IF (@transCount = 0)
    BEGIN
      COMMIT TRAN;
    END;


  END TRY

  BEGIN CATCH

    IF ((@transCount = 0) AND (XACT_STATE() <> 0))
    BEGIN
      ROLLBACK TRAN;
    END;

    SELECT @errorMessage = ERROR_MESSAGE(),
           @errorNumber = ERROR_NUMBER();

    INSERT dbo.ErrorLog(Error) VALUES(@errorMessage); --only this logging happens

    RAISERROR(N'Error in Caller.', 16, -1); 

    RETURN @errorNumber;

  END CATCH;

  RETURN;

END;
GO


IF OBJECT_ID(N'tempdb..#Called') IS NOT NULL
BEGIN
  DROP PROC #Called;
END;
GO

CREATE PROC #Called 
AS
BEGIN
  SET NOCOUNT ON;
  SET XACT_ABORT ON;

  DECLARE @transCount TINYINT = @@TRANCOUNT,
          @errorMessage NVARCHAR(4000),
          @errorNumber INT;

  BEGIN TRY

    IF (@transCount = 0) --doesn't start tran, already in one
    BEGIN
      BEGIN TRAN;
    END;

    SELECT 1/0; --generate an error; this exception gets lost

    IF (@transCount = 0)
    BEGIN
      COMMIT TRAN;
    END; 

  END TRY

  BEGIN CATCH 

    IF ((@transCount = 0) AND (XACT_STATE() <> 0)) --cannot rollback here because this didn't start the transaction
    BEGIN
      ROLLBACK TRAN;
    END;

    SELECT @errorMessage = ERROR_MESSAGE(),
           @errorNumber = ERROR_NUMBER();

    INSERT dbo.ErrorLog(Error) VALUES(@errorMessage); --doesn't happen because of uncommitable transaction; raises exception, caught in CATCH block of Caller 

    RAISERROR(N'Error in Called.', 16, -1); --this doesn't happen

    RETURN @errorNumber; --nothing returned

  END CATCH;

  RETURN;
END
GO


EXEC dbo.#Caller;
GO 

SELECT * FROM dbo.ErrorLog;
GO 

记录的单个错误只是不可提交的事务异常。有没有办法在 TRY..CATCH 中处理嵌套事务,并且仍然记录实际发生的错误?

【问题讨论】:

  • 我知道的唯一方法是使用表变量,因为它们不受回滚的影响,但如果您的逻辑很复杂,它可能会变得非常混乱。
  • 不确定这是否可行,嘿。错误日志应该是一个持久表。表变量将在执行后被清理。我还认为表变量插入在这里也不起作用,因为它是一个写操作,并且在不可提交状态下是不允许的。
  • 写入表变量必须在catch之外,或者至少我假设,以及在最终回滚后从表变量写入错误日志表。生存过程边界可能需要临时。表或其他东西来携带数据。当然,如果错误只是一行,输出参数应该可以工作。
  • 我所做的,而且似乎有帮助,就是将我的错误日志记录在 CATCH 中,并在 TRY..CATCH 中调用。如果日志记录失败,我只需通过原始异常传递。一个 RAISERROR 到执行日志记录的调用程序过程。

标签: sql-server tsql stored-procedures try-catch raiserror


【解决方案1】:

这种方法帮助我完成了我需要的事情。基本上,在注定要失败的 tran (XACT_STATE() = -1) 的情况下,整个事务会回滚以允许在两个级别进行错误记录,同时在执行结束时只引发一个异常。

IF OBJECT_ID(N'dbo.ErrorLog', N'U') IS NOT NULL
 DROP TABLE dbo.ErrorLog;
GO

CREATE TABLE dbo.ErrorLog (Error NVARCHAR(4000));
GO

IF OBJECT_ID(N'tempdb..#Caller') IS NOT NULL
BEGIN
  DROP PROC #Caller;
END;
GO

CREATE PROCEDURE #Caller 
AS
BEGIN
  SET NOCOUNT ON;
  SET XACT_ABORT ON;

  DECLARE @transCount TINYINT = @@TRANCOUNT,
          @returnCode INT,
          @errorMessage NVARCHAR(4000),
          @errorNumber INT;

  BEGIN TRY

    IF (@transCount = 0)
    BEGIN
      BEGIN TRAN;
    END;

    EXEC @returnCode = #Called;

    IF (@returnCode <> 0)
    BEGIN 
      RAISERROR(N'Error in Called. Caller returned an error', 16, -1); 
    END;

    IF (@transCount = 0)
    BEGIN
      COMMIT TRAN;
    END;


  END TRY

  BEGIN CATCH

    IF (((@transCount = 0) AND (XACT_STATE() <> 0)) OR (XACT_STATE() = -1))
    BEGIN
      ROLLBACK TRAN;
    END;

    SELECT @errorMessage = ERROR_MESSAGE(),
           @errorNumber = ERROR_NUMBER();

    INSERT dbo.ErrorLog(Error) VALUES(@errorMessage); --only this logging happens

    RAISERROR(N'Error in Caller.', 16, -1); 

    RETURN @errorNumber;

  END CATCH;

  RETURN;

END;
GO


IF OBJECT_ID(N'tempdb..#Called') IS NOT NULL
BEGIN
  DROP PROC #Called;
END;
GO

CREATE PROC #Called 
AS
BEGIN
  SET NOCOUNT ON;
  SET XACT_ABORT ON;

  DECLARE @transCount TINYINT = @@TRANCOUNT,
          @errorMessage NVARCHAR(4000),
          @errorNumber INT;

  BEGIN TRY

    IF (@transCount = 0) --doesn't start tran, already in one
    BEGIN
      BEGIN TRAN;
    END;

    SELECT 1/0; --generate an error; this exception gets lost

    IF (@transCount = 0)
    BEGIN
      COMMIT TRAN;
    END; 

  END TRY

  BEGIN CATCH 

    IF (((@transCount = 0) AND (XACT_STATE() <> 0)) OR (XACT_STATE() = -1)) --rollback even though didn't start this tran because its doomed
    BEGIN
      ROLLBACK TRAN;
    END;

    SELECT @errorMessage = ERROR_MESSAGE(),
           @errorNumber = ERROR_NUMBER();

    INSERT dbo.ErrorLog(Error) VALUES(@errorMessage); --doesn't happen because of uncommitable transaction; raises exception, caught in CATCH block of Caller 

    RAISERROR(N'Error in Called.', 16, -1); --this doesn't happen

    RETURN @errorNumber; --nothing returned

  END CATCH;

  RETURN;
END
GO


DECLARE @returnCode INT;
EXEC @returnCode = dbo.#Caller; --one exception raised
SELECT @returnCode AS 'returnCode'; --error code 50000 returned

SELECT * FROM dbo.ErrorLog; --both errors logged incl original exception

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-05-09
    • 2021-09-02
    • 2011-11-20
    • 1970-01-01
    • 1970-01-01
    • 2016-06-01
    • 1970-01-01
    • 2015-10-02
    相关资源
    最近更新 更多