sqlserver嵌套事物問題


問題描述:在做一個定時計算成績表的數據庫作業,其中TestCount記錄需要進行計算的考試記錄,然后進行循環計算,對每個考試計算都創建一個單獨事務,若失敗,則回滾。目前出現的問題是當Pro_Score_CountEverySubjectStandardRate出現錯誤后,Pro_Score_CountTestByOneKey存儲過程進行回滾時,得到錯誤信息為“ 當前事務無法提交,而且無法支持寫入日志文件的操作。請回滾該事務。”,也進行了XACT_State() <> -1的判斷還是不行,研究了很久不知道什么原因,請大家幫忙討論研究。

存儲過程Pro_Score_CountEverySubjectStandardRate由於有可能單獨執行,所以也需要加上事務處理,但又避免出現嵌套事務,難以控制,因此我思路為
1、當沒有嵌套事務時,直接創建事務,若出現錯誤則回滾
2、當調用該存儲過程的存儲過程已經打開事務則創建事務保存點,確保事務提交次數一致


ALTER PROCEDURE [dbo].[Pro_Score_CountTestByOneKey] 
@testId INT
AS
BEGIN
DECLARE @countState INT
DECLARE @tranName VARCHAR(50)
DECLARE @testCount INT
DECLARE @loopId INT
DECLARE @errInfo VARCHAR(MAX)
DECLARE @thisTestId         INT
DECLARE @startTime          DATETIME
DECLARE @endTime            DATETIME
DECLARE @persistTime        DATETIME
DECLARE @tranCounter        INT

SET @countState = 1
SET @tranName = 'countScoreData'
SET @testCount = 0
SET @loopId = 1
SET @errInfo = ''
SET @thisTestId         = 0
SET @startTime          = 0
SET @endTime            = 0
SET @persistTime        = 0
SET @tranCounter        = 0

DECLARE @TableTestCount TABLE(
    ID          INT IDENTITY(1,1), 
    testId      INT
)

/*讀取需要計算的考試記錄*/
IF @testId > 0
BEGIN
INSERT INTO @TableTestCount
SELECT DISTINCT countTestId   
FROM TestCount
WHERE countTestId = @testId 
END
ELSE
BEGIN
INSERT INTO @TableTestCount
SELECT DISTINCT countTestId   
FROM TestCount
WHERE successCountTime IS NUll
END

SELECT @testCount = COUNT(ID)
FROM @TableTestCount

IF @testCount > 0
BEGIN
WHILE(@loopId <= @testCount)
BEGIN
SELECT @thisTestId = testId
FROM @TableTestCount 
WHERE ID = @loopId

SET @tranCounter        = @@TRANCOUNT

PRINT '開始前='+STR(@@TRANCOUNT)

BEGIN TRY 
--事物鎖定
IF @tranCounter = 0
    BEGIN
        BEGIN TRAN @tranName
    END
ELSE
    BEGIN
        SAVE TRAN @tranName;
    END
PRINT '開始后='+STR(@@TRANCOUNT)
    
    

--第7步,計算各班各科的統分比率
EXEC dbo.Pro_Score_CountEverySubjectStandardRate        @thisTestId;


/*
--成績計算截止時間
SET @endTime        = GETDATE();
--計算成績計算持續時間
SET @persistTime    = @endTime - @startTime

--更新計算成功時間
UPDATE TestCount 
SET 
    successCountTime    = GETDATE(),
    persistTime         = @persistTime
WHERE countTestId = @thisTestId AND  successCountTime IS NULL

--判斷是否是前面計算過錯誤,再次進行計算
UPDATE TestCount 
SET 
    countAgain = 1
WHERE countTestId = @thisTestId AND failCountTime IS NOT NULL
 
SET @countState = 1
*/
IF @tranCounter = 0
    BEGIN
        COMMIT TRAN @tranName
    END
END TRY 

--若出現異常錯誤,回滾成績計算
BEGIN CATCH
    PRINT @tranName
    PRINT '結束前='+STR(@@TRANCOUNT)
    IF @tranCounter = 0 
        BEGIN
            ROLLBACK TRAN @tranName
        END
    ELSE
        IF XACT_STATE() <> -1
            BEGIN
                 ROLLBACK TRAN @tranName
            END
    
    PRINT '結束后='+STR(@@TRANCOUNT)
SET @errInfo = '錯誤號:'+ STR(ERROR_NUMBER()) + ', '+
   '錯誤嚴重級別:'+ STR(ERROR_SEVERITY()) + ', '+
   '錯位狀態:'+ STR(ERROR_STATE()) + ', '+
   '錯誤存儲過程和觸發器名稱:'+ ERROR_PROCEDURE() + ', '+
   '錯誤行號:'+ STR(ERROR_LINE()) + ', '+
   '錯誤實際信息:'+ ERROR_MESSAGE()
PRINT @errInfo

--更新計算失敗時間和原因
UPDATE TestCount 
SET failCountTime = GETDATE(), failReason = @errInfo
WHERE countTestId = @thisTestId

IF @countState = 1
    BEGIN
        SET @countState = 0
    END
END CATCH

SET @loopId += 1
END
END 
SELECT @countState
END


存儲過程:Pro_Score_CountEverySubjectStandardRate
ALTER      PROCEDURE [dbo].[Pro_Score_CountEverySubjectStandardRate]      
@testId INT
AS
BEGIN
SET NOCOUNT ON
DECLARE @statisticsResult INT
DECLARE @tranName VARCHAR(50)
DECLARE @standardCount INT
DECLARE @gradeId INT
DECLARE @tranCounter                INT

SET @statisticsResult = 0
SET @tranName = 'countEverySubjectStandardRate'
SET @standardCount = 0
SET @gradeId = 0
SET @tranCounter                    = @@TRANCOUNT

BEGIN TRY
    IF @tranCounter = 0
        BEGIN
        BEGIN TRAN @tranName;
    END
ELSE
    BEGIN
        SAVE TRAN @tranName;
    END;
    
--創建統分標准臨時表
WITH SubjectPointStandardCTE AS(
SELECT tP_SubjectId, tP_StandardName, tP_FullMark fullPoint,tP_PointDown, tP_PointUp
FROM HSL_PointStandard
WHERE tP_TestId = @testId 
)
SELECT ID = IDENTITY(INT,1,1),tP_SubjectId,tP_StandardName,fullPoint,tP_PointDown,
CASE WHEN  tP_PointUp = fullPoint THEN fullPoint + 1 ELSE tP_PointUp END tP_PointUp
INTO #TableSubjectStandard
FROM SubjectPointStandardCTE;

IF @tranCounter = 0
    BEGIN
        COMMIT TRAN @tranName
    END
END TRY

BEGIN CATCH  
    
    IF @tranCounter = 0
        BEGIN
        ROLLBACK   TRAN @tranName;
    END
ELSE
    IF XACT_STATE() <> -1
        BEGIN
            ROLLBACK   TRAN @tranName;
        END
SET @statisticsResult = 0
END CATCH

SELECT @statisticsResult
END

17 个解决方案

#1


子存儲過程中存在try catch的話 最好去掉 讓錯誤往上一級拋出,由上一級做處理.

另外,添加SET XACT_ABORT ON 做一下限定。 

#2


1.增加 SET XACT_ABORT ON
2.增加:IF (XACT_STATE()) = 1
    BEGIN
        COMMIT TRANSACTION;   
    END;

#3


 @tranCounter = 0這個需要改一下
有可提交的事務, 並且事務是在當前模塊中開啟的情況下, 才提交事務
IF XACT_STATE() = 1 AND @tranCounter  = 0
COMMIT;

#4


引用 1 樓 OrchidCat 的回復:
子存儲過程中存在try catch的話 最好去掉 讓錯誤往上一級拋出,由上一級做處理.

另外,添加SET XACT_ABORT ON 做一下限定。

XACT_ABORT應該比try catch 更難控制吧,遇到錯誤就終止了,直接回滾。
特別是嵌套事務中,有的是不能夠直接做回滾的

#5


 加上IF XACT_STATE() = 1 后會出現這樣的錯誤提示

消息 266,級別 16,狀態 2,過程 Pro_Score_CountTestByOneKey,第 194 行
EXECUTE 后的事務計數指示 BEGIN 和 COMMIT 語句的數目不匹配。上一計數 = 0,當前計數 = 1。
消息 3998,級別 16,狀態 1,第 1 行
在批處理結束時檢測到不可提交的事務。該事務將回滾。

#6


大家幫忙分析分析,真夠頭疼的

#7


引用 5 樓 zouqingfang 的回復:
加上IF XACT_STATE() = 1 后會出現這樣的錯誤提示

消息 266,級別 16,狀態 2,過程 Pro_Score_CountTestByOneKey,第 194 行
EXECUTE 后的事務計數指示 BEGIN 和 COMMIT 語句的數目不匹配。上一計數 = 0,當前計數 = 1。
消息 3998,級別 16,狀態 1,第 1 行
在批處理……


遇到這種情況,lz需要判斷一下當前有否未提交的事務,if @@trancount >0  ,然后再進行處理。

#8


引用 7 樓 OrchidCat 的回復:
引用 5 樓 zouqingfang 的回復:加上IF XACT_STATE() = 1 后會出現這樣的錯誤提示

消息 266,級別 16,狀態 2,過程 Pro_Score_CountTestByOneKey,第 194 行
EXECUTE 后的事務計數指示 BEGIN 和 COMMIT 語句的數目不匹配。上一計數 = 0,當前計數 = 1。
消息 3998,……

因為當子存儲過程出錯后在主存儲過程中
XACT_STATE() 就變成為-1了
IF @tranCounter = 0 AND XACT_STATE() = 1 
    BEGIN                           
         COMMIT TRAN @tranName
    END
所以就導致事務提交不匹配

#9


感覺 Pro_Score_CountEverySubjectStandardRate 里面沒必要用事務啊 

#10


引用 9 樓 lixzhong 的回復:
感覺 Pro_Score_CountEverySubjectStandardRate 里面沒必要用事務啊


這是因為Pro_Score_CountEverySubjectStandardRate 我在其他地方需要單獨調用,所以需要事務處理操作

#11


單獨調用 也不需要事務吧 里面沒有什么需要加事務的條件,就是一個返回結果。

#12


引用 11 樓 lixzhong 的回復:
單獨調用 也不需要事務吧 里面沒有什么需要加事務的條件,就是一個返回結果。


我實際執行中還要加入insert和update操作,上面只是一個簡單操作

#13


奇怪的是
我將Pro_Score_CountEverySubjectStandardRate中的try catch屏蔽,Pro_Score_CountTestByOneKey就能夠正常的捕獲Pro_Score_CountEverySubjectStandardRate中的錯誤,但msdn上的說明不是允許try catch嵌套操作

#14


ELSE
            IF XACT_STATE() <> -1
                BEGIN
                    ROLLBACK   TRAN ;
                END

這里應該改成 
IF XACT_STATE() = -1


XACT_STATE 為-1 時, 不能回滾到事務保存點, 這種情況留給外層調用者做統一的事務回滾

#15


引用 14 樓 知識青年 的回復:
ELSE
            IF XACT_STATE() <> -1
                BEGIN
                    ROLLBACK   TRAN ;
                END

這里應該改成 
IF XACT_STATE() = -1


XACT_STATE 為-1 時, 不能回滾到事務保……


   ROLLBACK   TRAN ;這里不要加那個事物名稱。

#16


引用 15 樓 知識青年 的回復:
引用 14 樓 知識青年 的回復:ELSE
            IF XACT_STATE() <> -1
                BEGIN
                    ROLLBACK   TRAN ;
                END

這里應該改成 
IF XACT_STATE() = -1


XACT_STAT……


非常感謝你,也感謝上面的各位,找到原因了,我對XACT_STATE()理解錯誤,判斷條件應該為XACT_STATE() = -1才正確

#17


引用 13 樓 zouqingfang 的回復:
奇怪的是
我將Pro_Score_CountEverySubjectStandardRate中的try catch屏蔽,Pro_Score_CountTestByOneKey就能夠正常的捕獲Pro_Score_CountEverySubjectStandardRate中的錯誤,但msdn上的說明不是允許try catch嵌套操作

那就作出一個副本,不用try 。

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com