【问题标题】:SQL Server SET XACT_ABORT ON vs TRY...CATCH Block inside the stored procedureSQL Server SET XACT_ABORT ON vs TRY...CATCH 存储过程中的块
【发布时间】:2019-08-04 21:52:05
【问题描述】:

我正在开发一个游戏数据库。我有一个由游戏客户端在传送、登录、注销、死亡等过程中执行的存储过程。游戏客户端是硬编码的,我无法编辑。

我正在我的程序中做一些事情,例如如果角色登录到游戏,然后将项目添加到角色的库存中。

对于每种不同类型的进程,我都有IF 块,并且每个“IF”块中都有TRY...CATCH 块,以便能够处理我的过程中的任何错误。

那么,我的问题是,以这种方式使用 TRY...CATCH 块是否有意义?或者我应该使用SET XACT_ABORT ON 语句而不是TRY...CATCH?哪一个更好?顺便说一句,IF块出现任何错误的情况,该块必须完全是ROLLBACK

另外,我的程序被游戏客户端高度执行。游戏中有近800个在线角色总是在移动并执行我的程序。它应该尽可能快地执行。

ALTER PROCEDURE [dbo].[_AddLogChar]
    @CharID  INT,
    @EventID TINYINT,
    @Data1   INT,
    @Data2   INT,
    @strPos  VARCHAR(64),
    @Desc    VARCHAR(128)
AS
    ---- !!! KILL PROCEDURE !!! ----
    IF (@EventID NOT IN (4,6,20))
    BEGIN
        RETURN 0;
    END

    ---- BATTLE ARENA | ACADEMY ----
    IF (@EventID = 20)
    BEGIN
        BEGIN TRY
            BEGIN TRANSACTION TRAN_Battle_Arena

            -- Declaration of variables for battle area conditions
            DECLARE @CharInBattle VARCHAR(64) = SUBSTRING(@strPos, 15, 6)

            IF (@CharInBattle IN ('0x7edc','0x7edb','0x7ed7','0x7ed3','0x7dd3','0x7ada','0x7ad8','0x7ad7','0x7ad5','0x7ad4','0x79db','0x79da','0x79d8','0x79d7','0x79d5','0x79d4','0x74d6','0x73d7','0x73d6','0x73d5','0x73d4','0x72d7','0x72d6','0x72d5','0x72d4'))
            BEGIN
                DECLARE @KillingCharname VARCHAR(50) = (SELECT SUBSTRING(@Desc,(PATINDEX('%(%', @Desc)) + 1, ((PATINDEX('%)%', @Desc)) - (PATINDEX('%(%', @Desc))) - 1)) 
                DECLARE @KilledCharname VARCHAR(64) = (SELECT CharName16 FROM SRO_VT_SHARD.._Char WITH (NOLOCK) WHERE CharID = @CharID)

                IF((@KillingCharname IS NOT NULL) AND (@KilledCharname IS NOT NULL))
                BEGIN
                    INSERT INTO LOG_BattleHonorRank (KillingCharname, KilledCharname, BattleRegion)  
                    VALUES (@KillingCharname, @KilledCharname, @CharInBattle)

                    UPDATE SRO_VT_SHARD.._TrainingCamp 
                    SET GraduateCount = (GraduateCount + 1),
                        EvaluationPoint = EvaluationPoint + 5 
                    WHERE ID = (SELECT CampID FROM SRO_VT_SHARD.._TrainingCampMember WITH (NOLOCK) WHERE CharName = @KillingCharname)

                    UPDATE SRO_VT_SHARD.._TrainingCamp 
                    SET EvaluationPoint = EvaluationPoint - 6 
                    WHERE ID = (SELECT CampID FROM SRO_VT_SHARD.._TrainingCampMember WITH (NOLOCK) WHERE CharName = @KilledCharname)
                END
            END

            COMMIT TRANSACTION TRAN_Battle_Arena
        END TRY
        BEGIN CATCH
            SELECT
                ERROR_LINE() AS ErrorLine,
                ERROR_MESSAGE() AS ErrorMessage;

            ROLLBACK TRANSACTION TRAN_Battle_Arena
        END CATCH

        RETURN 1;
    END

    ---- JOB SYSTEM ----
    IF(@EventID=6 AND (SELECT [Level] FROM SRO_VT_SHARD.dbo._CharTrijob WHERE CharID=@CharID)=7)
    BEGIN
    BEGIN TRY
        BEGIN TRANSACTION TRAN_Job_System

        ---------------------------------------------------------------------------------------------------
        -- Declaration of variables
        ---------------------------------------------------------------------------------------------------

        DECLARE @Charname16 VARCHAR(64)=(SELECT Charname16 FROM SRO_VT_SHARD.dbo._Char WITH (NOLOCK) WHERE CharID=@CharID)
        DECLARE @traderJID INT=(SELECT UserJID FROM SRO_VT_SHARD.dbo._User WITH (NOLOCK) WHERE CharID=@CharID)
        DECLARE @SkillID INT
        DECLARE @JobBuffLevel INT

        ---------------------------------------------------------------------------------------------------
        -- Check users have any information in SK_Silk or not, if not then begin to addition
        ---------------------------------------------------------------------------------------------------

        IF NOT EXISTS(SELECT JID FROM [SRO_VT_ACCOUNT].[dbo].[SK_Silk] WHERE JID=@traderJID)
        BEGIN
            INSERT INTO [SRO_VT_ACCOUNT].[dbo].[SK_Silk] (JID, silk_own, silk_gift, silk_point) VALUES(@traderJID, 0, 0, 0);
        END

        ---------------------------------------------------------------------------------------------------
        -- Check users have any information in LOG_CharJobStatus or not, if not then begin to addition
        ---------------------------------------------------------------------------------------------------

        IF NOT EXISTS(SELECT CharID FROM SRO_VT_LOG..LOG_CharJobStatus WHERE CharID=@CharID)
        BEGIN
            INSERT INTO SRO_VT_LOG..LOG_CharJobStatus (CharID, Charname, RestartCount, ObtainedSilk) VALUES(@CharID, @Charname16, 0, 0)
        END

        ---------------------------------------------------------------------------------------------------
        -- Begin to add reward silk, restart count, obtained silk & job coins information
        ---------------------------------------------------------------------------------------------------

            UPDATE [SRO_VT_ACCOUNT].[dbo].[SK_Silk] SET silk_own=(silk_own+10) WHERE JID=@traderJID;
            UPDATE SRO_VT_LOG..LOG_CharJobStatus SET RestartCount=(RestartCount+1), ObtainedSilk=(ObtainedSilk+10), [Date]=GETDATE() WHERE CharID=@CharID
            EXEC SRO_VT_SHARD.dbo._ADD_ITEM_EXTERN @Charname16,'ITEM_ETC_SD_TOKEN_02',4,0

        ---------------------------------------------------------------------------------------------------
        -- Check users restart count modulus, if modulus 10 equals to 0, then begin to add advanced elixir scroll
        ---------------------------------------------------------------------------------------------------

        IF((SELECT (RestartCount % 10) FROM SRO_VT_LOG..LOG_CharJobStatus WHERE CharID=@CharID)=0)
        BEGIN
            UPDATE SRO_VT_LOG..LOG_CharJobStatus SET Obtained_Advanced_Elixir=(Obtained_Advanced_Elixir+1), [Date]=GETDATE() WHERE CharID=@CharID
            EXEC SRO_VT_SHARD.dbo._ADD_ITEM_EXTERN @Charname16,'ITEM_ETC_VENUS_ADVANCED_ELIXIR_SCROLL',1,0
        END

        ---------------------------------------------------------------------------------------------------
        -- Check users restart count modulus, if modulus 5 equals to 0, then begin to add job buff
        ---------------------------------------------------------------------------------------------------

        IF((SELECT (RestartCount % 5) FROM SRO_VT_LOG..LOG_CharJobStatus WHERE CharID=@CharID)=0)
        BEGIN

            IF EXISTS (SELECT JobID FROM SRO_VT_SHARD.._TimedJob WITH (NOLOCK) WHERE CharID=@CharID AND JobID IN (33791,33792,33793,33794,33795,33796,33797,33798,33799,33800))
            BEGIN
                DELETE FROM SRO_VT_SHARD.._TimedJob WHERE CharID=@CharID AND JobID IN (33791,33792,33793,33794,33795,33796,33797,33798,33799,33800)
            END

            IF((SELECT BuffLevel FROM SRO_VT_LOG..LOG_CharJobStatus WHERE CharID=@CharID)<=10)
            BEGIN
                UPDATE SRO_VT_LOG..LOG_CharJobStatus SET BuffLevel=(BuffLevel+1), [Date]=GETDATE() WHERE CharID=@CharID
            END

            SET @JobBuffLevel=(SELECT BuffLevel FROM SRO_VT_LOG..LOG_CharJobStatus WHERE CharID=@CharID)

            SELECT @SkillID=
            (CASE
                WHEN @JobBuffLevel=1 THEN 33791
                WHEN @JobBuffLevel=2 THEN 33792
                WHEN @JobBuffLevel=3 THEN 33793
                WHEN @JobBuffLevel=4 THEN 33794
                WHEN @JobBuffLevel=5 THEN 33795
                WHEN @JobBuffLevel=6 THEN 33796
                WHEN @JobBuffLevel=7 THEN 33797
                WHEN @JobBuffLevel=8 THEN 33798
                WHEN @JobBuffLevel=9 THEN 33799
                WHEN @JobBuffLevel>=10 THEN 33800
                ELSE 0
            END)

            IF (NOT EXISTS (SELECT JobID FROM SRO_VT_SHARD.._TimedJob WHERE JobID=@SkillID AND CharID=@CharID) AND (@SkillID>0))
            BEGIN
                INSERT INTO SRO_VT_SHARD.._TimedJob VALUES (@CharID,0,@SkillID,(SELECT DATEDIFF(SECOND,'19700101 00:00:00:000',(SELECT DATEADD(HOUR,72,GETUTCDATE())))),0,1,0,0,0,0,0,0,0,0)
            END

        END

        ---------------------------------------------------------------------------------------------------
        -- Restart to users job level
        ---------------------------------------------------------------------------------------------------

        UPDATE SRO_VT_SHARD.._CharTrijob SET [Level]=1, [Exp]=0, Contribution=0 WHERE CharID=@CharID

        COMMIT TRANSACTION TRAN_Job_System
    END TRY
    BEGIN CATCH
        SELECT
            ERROR_LINE() AS ErrorLine,
            ERROR_MESSAGE() AS ErrorMessage;
        ROLLBACK TRANSACTION TRAN_Job_System
    END CATCH
    END
----==========================================================================================================----
    -------------------------------------------- SILKPERPERIOD -----------------------------------------------
    IF(@EventID=4 OR @EventID=6)
    BEGIN
    BEGIN TRY
        BEGIN TRANSACTION TRAN_SilkPerPeriod

        ---------------------------------------------------------------------------------------------------
        -- For login state
        ---------------------------------------------------------------------------------------------------

        IF (@EventID=4)
        BEGIN
            IF NOT EXISTS(SELECT CharID FROM LOG_CharInOut WHERE CharID=@CharID)
            BEGIN
                INSERT INTO LOG_CharInOut (CharID,Char_Name,Is_Online,In_Date) VALUES(@CharID, (SELECT CharName16 FROM SRO_VT_SHARD.._Char WITH(NOLOCK) WHERE CharID=@CharID), 1, GETDATE());
            END
            IF EXISTS(SELECT CharID FROM LOG_CharInOut WHERE CharID=@CharID)
            BEGIN
                UPDATE LOG_CharInOut SET Is_Online=1, In_Date=GETDATE() WHERE CharID=@CharID
            END
        END

        ---------------------------------------------------------------------------------------------------
        -- For logout state
        ---------------------------------------------------------------------------------------------------

        IF (@EventID=6)
        BEGIN
            DECLARE @SilkQuantity INT=1 -- Quantity of silk to be given within the specified period.
            DECLARE @ReqTime INT=60 -- The minimum required online period in minutes to be awarded for the silk reward.
            UPDATE LOG_CharInOut SET Is_Online=0, Out_Date=GETDATE() WHERE CharID=@CharID
            DECLARE @JID INT=(SELECT UserJID FROM SRO_VT_SHARD.dbo._User WITH (NOLOCK) WHERE CharID=@CharID)
            DECLARE @LastOnlineTime INT=(SELECT DATEDIFF(MINUTE,(SELECT In_Date FROM LOG_CharInOut WHERE CharID=@CharID),(SELECT Out_Date FROM LOG_CharInOut WHERE CharID=@CharID)))
            UPDATE LOG_CharInOut SET Last_OnlineTime=@LastOnlineTime, Total_OnlineTime=Total_OnlineTime+@LastOnlineTime WHERE CharID=@CharID
            DECLARE @TotalOnlineTime INT, @UsedOnlineTime INT;
            SELECT @TotalOnlineTime=Total_OnlineTime , @UsedOnlineTime=Used_OnlineTime FROM LOG_CharInOut WHERE CharID=@CharID

            IF NOT EXISTS(SELECT JID FROM SRO_VT_ACCOUNT..SK_Silk WHERE JID=@JID)
            BEGIN
                INSERT INTO SRO_VT_ACCOUNT..SK_Silk (JID, silk_own, silk_gift, silk_point) VALUES(@JID, 0, 0, 0);
            END

            IF EXISTS(SELECT JID FROM SRO_VT_ACCOUNT..SK_Silk WHERE JID=@JID)
            BEGIN
                IF ((CONVERT(INT,@TotalOnlineTime-@UsedOnlineTime)/@ReqTime)>0)
                BEGIN
                    UPDATE SRO_VT_ACCOUNT..SK_Silk SET silk_point=silk_point+(CONVERT(INT,((@TotalOnlineTime-@UsedOnlineTime)/@ReqTime))*@SilkQuantity) WHERE JID=@JID
                    UPDATE LOG_CharInOut SET Used_OnlineTime=Used_OnlineTime+((CONVERT(INT,((@TotalOnlineTime-@UsedOnlineTime)/@ReqTime)))*@ReqTime) WHERE CharID=@CharID
                END
            END
        END

        COMMIT TRANSACTION TRAN_SilkPerPeriod
    END TRY
    BEGIN CATCH
        SELECT
            ERROR_LINE() AS ErrorLine,
            ERROR_MESSAGE() AS ErrorMessage;
        ROLLBACK TRANSACTION TRAN_SilkPerPeriod
    END CATCH
    END
----==========================================================================================================----
    ---------------------------------------------- STAT RESET ------------------------------------------------
    IF(@EventID=6 AND EXISTS(SELECT CharID FROM SRO_VT_LOG..LOG_CharStat WHERE CharID=@CharID))
    BEGIN
        DECLARE @RebirthCountForStat INT=(SELECT RebirthCount FROM SRO_VT_LOG..LOG_CharRebirth WITH (NOLOCK) WHERE CharID=@CharID)
        DECLARE @MaxLevel TINYINT=(SELECT MaxLevel FROM SRO_VT_SHARD.._Char WITH (NOLOCK) WHERE CharID=@CharID)
        DECLARE @StatPoint SMALLINT, @RemainStatPoint SMALLINT

        SET @StatPoint=
        (CASE
            WHEN @RebirthCountForStat IS NULL   THEN @MaxLevel+19
            WHEN @RebirthCountForStat <= 5      THEN @MaxLevel+(@RebirthCountForStat*6)+19
            WHEN @RebirthCountForStat > 5       THEN @MaxLevel+49
            ELSE @MaxLevel+19
        END)

        SET @RemainStatPoint = (@MaxLevel*3)-3

        UPDATE SRO_VT_SHARD.._Char SET Strength=@StatPoint, Intellect=@StatPoint, RemainStatPoint=@RemainStatPoint WHERE CharID=@CharID

        DELETE FROM SRO_VT_LOG..LOG_CharStat WHERE CharID=@CharID
    END
----==========================================================================================================----
    -------------------------------------------- REBIRTH SYSTEM ----------------------------------------------
    IF(@EventID=6)
    BEGIN

        DECLARE @RebirthCount INT=(SELECT RebirthCount FROM SRO_VT_LOG..LOG_CharRebirth WHERE CharID=@CharID)
        DECLARE @Is_Active TINYINT=(SELECT Is_Active FROM SRO_VT_LOG..LOG_CharRebirth WHERE CharID=@CharID)

        IF(@Is_Active=1 AND @RebirthCount<=5)-- Rebirth Count Limitation-1
        BEGIN
            UPDATE SRO_VT_SHARD.._Char SET
            CurLevel=1,
            MaxLevel=1,
            ExpOffset=0,
            SExpOffset=0,
            Strength=20+(@RebirthCount*6),
            Intellect=20+(@RebirthCount*6),
            RemainSkillPoint=0,
            RemainStatPoint=0
            WHERE SRO_VT_SHARD.._Char.CharID=@CharID

            DELETE CS FROM SRO_VT_SHARD.._RefSkill RS INNER JOIN SRO_VT_SHARD.._CharSkill CS ON CS.CharID=@CharID AND RS.ID=CS.SkillID AND RS.ReqCommon_MasteryLevel1<=110 AND RS.ID NOT IN (1,70,40,2,8421,9354,9355,11162,9944,8419,8420,11526,10625)
            UPDATE SRO_VT_SHARD.._CharSkillMastery SET [Level]=0 WHERE CharID=@CharID AND [Level]<=110

            UPDATE SRO_VT_LOG..LOG_CharRebirth SET Is_Active=0 WHERE CharID=@CharID
        END

    END
----==========================================================================================================----
--################################################################################################################```

【问题讨论】:

  • 在这个存储过程中发生了太多事情,首先我会将这段代码分成三个单独的存储过程来处理事件 ID 4、6 和 20,然后有一个单独的 TRY 块,具体取决于调用三个存储过程之一的 EventID 值和一个 CATCH 块以回滚在 try 块中调用的存储过程。最佳实践是在存储过程中做一件事。代码更易于管理,并且在需要时可以轻松添加更多功能。
  • @M.Ali 设置 xact_abort on vs try..catch 怎么样?
  • 总是尝试捕捉。
  • 必读Error and Transaction Handling in SQL Server,作者是 Erland Sommarskog。你最好同时使用SET XACT_ABORT ONTRY CATCH
  • 我同意@VladimirBaranov。 XACT_ABORT ON 的好处也在于它会在查询超时的情况下立即回滚事务,此时CATCH 块没有被执行。

标签: sql-server tsql stored-procedures


【解决方案1】:

你应该同时使用这两个。让我们创建一个简单的表格来说明原因并回答几个基本问​​题。

DROP TABLE IF EXISTS [dbo].[StackOverflow];

CREATE TABLE [dbo].[StackOverflow]
(
    [StackID] TINYINT
);

现在,执行以下语句(一起):

INSERT INTO [dbo].[StackOverflow] ([StackID])
VALUES (104);

INSERT INTO [dbo].[StackOverflow] ([StackID])
VALUES (256);

SELECT [StackID]
FROM [dbo].[StackOverflow];

你会得到一个错误,因为第二次插入试图插入值,该值不能存储在TINYINT 类型中。

ACID 事务有四个定义它的属性。第一个是Atomacity

原子事务是一组不能被 彼此分开,必须作为一个单元处理 工作。

了解以上内容,可以认为引擎必须回滚两个插入,但不会。为什么?

因为在 SQL Server 的上下文中,有四种控制事务的方法:

  • 自动提交
  • 隐式
  • 显式
  • 批处理范围

默认是auto-commit:

任何改变数据并自行执行的语句都是 自动进行原子事务。更改是否影响一个 行或数千行,它必须为每一行成功完成 承诺。您不能手动回滚自动提交 交易。

因此 - 上述两个插入是两个独立的事务,其中第一个被提交,第二个不被提交。

那么,让我们使用implicit事务应用BEGINCOMMIT关键字来定义事务主体:

BEGIN TRANSACTION;

INSERT INTO [dbo].[StackOverflow] ([StackID])
VALUES (105);

INSERT INTO [dbo].[StackOverflow] ([StackID])
VALUES (256);

COMMIT TRANSACTION;

SELECT [StackID]
FROM [dbo].[StackOverflow];

所以,现在引擎将回滚两个插入,对吧?当然 - 不会。为什么?

因为,当XACT_ABORT IS OFF(这是默认值):

当 SET XACT_ABORT 为 OFF 时,在某些情况下只有 Transact-SQL 引发错误的语句被回滚并且事务 继续处理。

当它是ON:

.. 如果 Transact-SQL 语句引发运行时错误,则整个 事务被终止并回滚。

这就是我们需要的,如果您尝试下面的代码,您可以检查一下:

SET XACT_ABORT ON;

BEGIN TRANSACTION;

INSERT INTO [dbo].[StackOverflow] ([StackID])
VALUES (105);

INSERT INTO [dbo].[StackOverflow] ([StackID])
VALUES (256);

COMMIT TRANSACTION;

SET XACT_ABORT OFF;

SELECT [StackID]
FROM [dbo].[StackOverflow];

那么,这就够了吗?答案是否定的——因为:

编译错误,例如语法错误,不受 SET 影响 XACT_ABORT。

这里第一个语句被提交,第二个 - 没有。

SET XACT_ABORT ON;

BEGIN TRANSACTION;

INSERT INTO [dbo].[StackOverflow] ([StackID])
VALUES (106);

EXECUTE
('
InnnNSERT INTO [dbo].[StackOverflow] ([StackID])
VALUES (256);
');

COMMIT TRANSACTION;

SET XACT_ABORT OFF;

SELECT [StackID]
FROM [dbo].[StackOverflow];

我在执行 CRUD 时使用的模板是:

SET NOCOUNT, XACT_ABORT ON;

BEGIN TRY;

    BEGIN TRANSACTION;

    COMMIT TRANSACTION;

END TRY
BEGIN CATCH

    IF @@TRANCOUNT > 0
    BEGIN;
        ROLLBACK TRANSACTION;
    END;

    THROW; -- or log error or something else

END CATCH;

SET NOCOUNT, XACT_ABORT OFF;

您可以查看Transaction Locking and Row Versioning Guide了解更多详情。

【讨论】:

  • 那么,我可以在开始和提交您共享上一个模板的事务之间使用所有IF 块吗?它会导致任何性能问题吗?此外,一些IF 块包含RETURN 语句。您的模板中是否仍需要它?
  • begin-commit 块中只保留CRUD 操作通常会更好。准备部分可以在begin trybegin transaction 之间——例如,您可以读取一些数据,对其进行修改、聚合,然后只将insert 部分放入事务块中。您可以安全地使用上面包裹在IF 中的模板——当然SET 部分可以保留在请求和存储过程的末尾。
  • Here (dba.stackexchange.com/questions/128535/…) Max Vernon 在他的回答中说,可能会出现 ROLLBACK 在 CATCH 内不起作用的情况,因此检查 XACT_STATE() 是明智的CATCH 块(此处的示例 C:docs.microsoft.com/en-us/sql/t-sql/language-elements/…)。
猜你喜欢
  • 2015-03-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-12
  • 1970-01-01
  • 2016-01-28
相关资源
最近更新 更多