1

据此您可以在 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 中处理嵌套事务,并且仍然记录实际发生的错误?

4

1 回答 1

0

这种方法帮助我完成了我所需要的。基本上,在一个注定失败的 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
于 2015-06-02T11:25:08.790 回答