如何在不结束SQL Server存储过程执行的情况下在内部事务中发生异常?

问题描述 投票:0回答:1

我的目标是将一个异常抛回给调用者,但继续执行SQL Server存储过程。所以,从本质上讲,我想要完成的是try..catch..finally block,即使SQL Server没有try..catch..finally块的概念,据我所知。

我有一个示例stored procedure来说明。这只是我想出的一个例子,所以请不要过多关注表模式。希望你能理解我在这里要做的事情的要点。无论如何,存储过程包含一个explicit transaction,在exception内投掷catch block。如果执行THROW,则会在try..catch块之后执行进一步执行,但它永远不会执行。据我所知,至少在SQL Server中,THROW无法区分内部和外部事务或嵌套事务。

在这个存储过程中,我有两个表:Tbl1和Tbl2。 Tbl1在Tbl1.ID上有一个primary key。 Tbl2在EmpFK上有一个映射到Tbl1.ID的foreign key。 EmpID有一个独特的约束。没有重复的记录可以插入到Tbl1中。 Tbl1和Tbl2都具有ID上的主键并且使用身份增量来自动插入。存储过程有三个输入参数,其中一个是employeeID。

在内部事务中,在Tbl1中插入记录 - 添加新的员工ID。如果失败,那么想法是事务应该优雅地错误输出但是存储过程应该仍然继续运行直到完成。无论表插入成功还是失败,EmpID都将在以后用于填写EmpFk。

在try..catch块之后,我通过传递给存储过程的employeeID参数执行Tbl1.ID的查找。然后,我在TBl2中插入一条记录; Tbl1.ID是Tbl2.EmpFK的值。

(你可能会问“为什么要使用这样的架构?为什么不将这个小数据集合并到一个表中呢?”再次,这只是一个例子。它不一定是员工。你可以选择任何东西。它只是一个小部件。想象一下,Tbl1可能包含一个非常非常大的数据集。一成不变的是有两个表具有主键/外键关系。)

这是示例数据集:

Tbl1
ID EmpID
1  AAA123
2  AAB123
3  AAC123

Tbl2
ID Role        Location EmpFK
1  Junior      NW       1
2  Senior      NW       2
3  Manager     NE       2
4  Sr Manager  SE       3
5  Director    SW       3

这是示例存储过程:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

CREATE PROCEDURE [dbo].[usp_TestProc]

    @employeeID VARCHAR(10)
    ,@role VARCHAR(50)
    ,@location VARCHAR(50)

AS
BEGIN

    SET NOCOUNT ON;

    DECLARE @employeeFK INT;

    BEGIN TRY
        BEGIN TRANSACTION MYTRAN;

            INSERT [Tbl1] (
                [EmpID]
            )
            VALUES (
                @employeeID
            );

        COMMIT TRANSACTION MYTRAN;
    END TRY

    BEGIN CATCH

        IF @@TRANCOUNT > 0
        BEGIN

            ROLLBACK TRANSACTION MYTRAN;

        END;

        THROW; -- Raises exception, exiting stored procedure

    END CATCH;

    SELECT
        @employeeFK = [ID]
    FROM
        [Tbl1]
    WHERE
        [EmpID] = @employeeID;

    INSERT [Tbl2] (
        [Role]
        ,[Location]
        ,[EmpFK]
    )
    VALUES (
        @role
        ,@location
        ,@employeeFK
    );

END;

所以,再次,我仍然希望将错误返回给调用者,即记录错误,但我不希望它在其轨道中冷却存储过程执行。它应该继续非常类似于try..catch..finally块。这可以用THROW完成,还是我必须使用其他方法?

也许我错了,但THROW不是RAISERROR的升级版本,并且,未来,我们应该使用前者来处理异常?

我曾经在这些情况下使用过RAISERROR,这很适合我。但THROW是一个更简单,更优雅的解决方案,imo,可能是更好的实践。我不太确定。

提前谢谢你的帮助。

sql-server exception stored-procedures transactions throw
1个回答
1
投票

一成不变的是有两个表具有主键/外键关系。

在内部事务中使用THROW并不是你想要的方式。从您的代码判断,您希望插入一个新员工,除非该员工已经存在,然后,无论员工是否已经存在,您都希望在第二次插入子表中使用该员工的PK / ID。

一种方法是拆分逻辑。这是我的意思的伪代码:

IF NOT EXISTS(Select employee with @employeeId)
  INSERT the new employee

SELECT @employeeFK like you are doing.

INSERT into Table2 like you are doing.

如果在传递已存在的@employeeId时仍然需要引发错误,可以在IF之后放置一个ELSE,并填充一个字符串变量,并在proc结束时,如果填充了该变量,则抛出/提出错误。

© www.soinside.com 2019 - 2024. All rights reserved.