Sql server SQL Server在存储过程内的vs TRY…CATCH块上设置XACT_ABORT

Sql server SQL Server在存储过程内的vs TRY…CATCH块上设置XACT_ABORT,sql-server,tsql,stored-procedures,Sql Server,Tsql,Stored Procedures,我正在制作一个游戏数据库。我有一个存储过程,在传送、登录、注销、死亡等过程中由游戏客户端执行。游戏客户端是硬编码的,我无法编辑 我在我的过程中做一些事情,比如如果角色登录到游戏中,然后将物品添加到角色的库存中 对于每种不同类型的进程,我都有IF块,而且我在每个IF块中都有TRY…CATCH块,以便能够处理过程中的任何错误 所以,我的问题是,以这种方式使用TRY…CATCH块是否有意义?或者我应该在语句上使用SET XACT_ABORT而不是TRY…CATCH?哪一个更好?顺便说一下,如果在IF块

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

我在我的过程中做一些事情,比如如果角色登录到游戏中,然后将物品添加到角色的库存中

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

所以,我的问题是,以这种方式使用TRY…CATCH块是否有意义?或者我应该在语句上使用SET XACT_ABORT而不是TRY…CATCH?哪一个更好?顺便说一下,如果在IF块中出现任何错误,该块必须完全回滚

此外,我的程序由游戏客户端高度执行。几乎有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
----==========================================================================================================----
--################################################################################################################```

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

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事务有四个属性定义它。第一个是原子性:

原子事务是一组不可更改的事件 相互分离,必须作为一个单独的设备进行处理 工作

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

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

自动提交 含蓄的 明确的 批量范围 默认设置为自动提交:

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

因此,上述两个插入是两个独立的事务,其中第一个已提交,第二个未提交

因此,让我们使用隐式事务应用BEGIN和COMMIT关键字来定义事务体:

BEGIN TRANSACTION;

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

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

COMMIT TRANSACTION;

SELECT [StackID]
FROM [dbo].[StackOverflow];
所以,有一件事是,现在引擎将回滚两个插入,对吗?当然,它不会。为什么?

因为,当默认值为:

当SET XACT_ABORT处于禁用状态时,在某些情况下,只有Transact-SQL 引发错误的语句将回滚,并且事务 继续处理

当它打开时:

。。如果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];
那么,这足够了吗?答案是否定的——因为:

编译错误(如语法错误)不受集合的影响 XACT_中止

这里第一个语句已提交,第二个语句未提交

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;

您可以查看以了解更多详细信息。

这一存储过程中有太多内容,首先,我将此代码分成三个单独的存储过程来处理事件ID 4、6和20,然后有一个TRY块,根据EventID值调用三个存储过程中的一个,一个CATCH块回滚TRY块中调用的存储过程。最佳实践是在存储过程中执行一件事。代码更易于管理,如果需要,可以很容易地添加更多功能。@M.Ali如何设置xact_abort on vs try..catch?始终尝试catch。必须由Erland Sommarskog读取。你最好同时使用SET XACT_ABORT和TRY CATCH。我同意@VladimirBaranov的观点。XACT_ABORT ON的好处也在于,在查询超时的情况下,它会立即回滚事务,此时不会执行CATCH块。因此,我可以使用begin和commit事务之间的所有IF块吗?您共享了上一个模板?这会导致性能问题吗?此外,一些IF块包含返回语句。模板中是否仍然需要它?通常最好只在begin commit块中保留CRUD操作。准备部分可以介于begin try和begin事务之间-例如,您可以读取一些数据、修改数据、聚合数据,然后仅将插入部分放入事务块中。您可以安全地使用上面包装在IF中的模板——当然,设置的部分可以只保留在beg和存储过程的末尾。这里,Max Vernon在回答中说,有可能出现回滚在CATCH中不起作用的情况,因此检查XACT_状态i是一种明智的做法 n此处的捕获块示例C:。