2015-05-19 68 views
1

根據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 

記錄單錯誤只是uncommitable交易例外。有沒有辦法處理TRY..CATCH中的嵌套事務,還有實際發生的日誌錯誤?

+1

我所知道的唯一的辦法就是因爲那些不受回滾使用表變量,但它可能會帶來麻煩,如果你的邏輯是複雜的。 –

+0

不知道這會工作嘿。錯誤日誌應該是一個持久表。表變量在執行後會被清理乾淨。我也認爲一個表變量插入也不會在這裏工作,因爲它是一個寫操作,並且這些不允許在一個不可承諾的狀態。 – Knightwisp

+0

寫入表變量必須在catch之外,或者至少我假設,並且在最終回滾之後將表變量寫入errorlog表。倖存的程序邊界可能會需要溫度。表或其他東西來傳輸數據。當然,如果錯誤只是一行,輸出參數應該可以工作。 –

回答

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