如何在不结束 SQL 服务器存储过程执行的情况下在内部事务中抛出异常?
How to THROW exception in inner transaction without ending SQL Server stored procedure execution?
我的objective 是将异常抛回给调用者,但继续执行SQL Server
存储过程。因此,本质上,据我所知,尽管 SQL 服务器没有 try..catch..finally 块的概念,但我要完成的是 try..catch..finally block
。
我有一个示例 stored procedure
来说明。这只是我想出的一个例子,所以请不要过多关注 table 架构。希望您理解我在这里要执行的操作的要点。无论如何,存储过程包含一个 explicit transaction
,它在 catch block
中抛出一个 exception
。如果 THROW
被执行,则在 try..catch 块之后还有进一步的执行,但它永远不会执行。据我了解,至少在SQL服务器中,THROW无法区分内部和外部事务或嵌套事务。
在这个存储过程中,我有两个table:Tbl1和Tbl2。 Tbl1 在 Tbl1.ID 上有一个 primary key
。 Tbl2 在 EmpFK 上有一个 foreign key
映射到 Tbl1.ID。 EmpID 具有唯一约束。 Tbl1 中不能插入重复记录。 Tbl1 和 Tbl2 都在 ID 上有主键,并使用身份增量进行自动插入。存储过程有三个输入参数,其中一个是employeeID.
在内部事务中,在 Tbl1 中插入了一条记录 -- 添加了一个新的员工 ID。如果它失败了,这个想法是事务应该优雅地出错,但存储过程应该仍然继续 运行 直到完成。不管table插入成功还是失败,后面都会使用EmpID填写EmpFk.
在 try..catch 块之后,我通过传递的 employeeID 参数执行 Tbl1.ID 的查找进入存储过程。然后,我向 TBl2 中插入一条记录; Tbl1.ID 是 Tbl2.EmpFK.
的值
(你可能会问 "why use such a schema? Why not combine into one table with such a small dataset?" 同样,这只是一个例子。它不一定是员工。你可以选择任何东西。它只是一个小部件。想象一下 Tbl1 可能包含非常非常大的数据集。确定的是有两个 table 具有主键/外键关系。)
这是示例数据集:
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;
所以,我还是想 return 将错误发送给调用者,即记录错误,但我不希望它停止执行存储过程。它应该继续非常类似于 try..catch..finally 块。这可以用 THROW
来完成,还是我必须使用其他方法?
也许我弄错了,但 THROW
不是 RAISERROR
的升级版本吗?今后,我们应该使用前者来处理异常吗?
我过去曾在这些情况下使用过 RAISERROR
,它非常适合我。但 THROW
是一个更简单、更优雅的解决方案,imo,并且可能是未来更好的实践。我不太确定。
提前感谢您的帮助。
What's set in stone is there are two tables which have a primary key /
foreign key relationship.
在内部事务中使用 THROW 不是做你想做的事情的方法。从您的代码来看,您想要插入一个新员工,除非该员工已经存在,然后,无论该员工是否已经存在,您都希望在第二次插入子项时使用该员工的 PK/id table.
一种方法是拆分逻辑。这是我的意思的伪代码:
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的最后,如果变量是填充,然后 throw/raise 一个错误。
我的objective 是将异常抛回给调用者,但继续执行SQL Server
存储过程。因此,本质上,据我所知,尽管 SQL 服务器没有 try..catch..finally 块的概念,但我要完成的是 try..catch..finally block
。
我有一个示例 stored procedure
来说明。这只是我想出的一个例子,所以请不要过多关注 table 架构。希望您理解我在这里要执行的操作的要点。无论如何,存储过程包含一个 explicit transaction
,它在 catch block
中抛出一个 exception
。如果 THROW
被执行,则在 try..catch 块之后还有进一步的执行,但它永远不会执行。据我了解,至少在SQL服务器中,THROW无法区分内部和外部事务或嵌套事务。
在这个存储过程中,我有两个table:Tbl1和Tbl2。 Tbl1 在 Tbl1.ID 上有一个 primary key
。 Tbl2 在 EmpFK 上有一个 foreign key
映射到 Tbl1.ID。 EmpID 具有唯一约束。 Tbl1 中不能插入重复记录。 Tbl1 和 Tbl2 都在 ID 上有主键,并使用身份增量进行自动插入。存储过程有三个输入参数,其中一个是employeeID.
在内部事务中,在 Tbl1 中插入了一条记录 -- 添加了一个新的员工 ID。如果它失败了,这个想法是事务应该优雅地出错,但存储过程应该仍然继续 运行 直到完成。不管table插入成功还是失败,后面都会使用EmpID填写EmpFk.
在 try..catch 块之后,我通过传递的 employeeID 参数执行 Tbl1.ID 的查找进入存储过程。然后,我向 TBl2 中插入一条记录; Tbl1.ID 是 Tbl2.EmpFK.
的值(你可能会问 "why use such a schema? Why not combine into one table with such a small dataset?" 同样,这只是一个例子。它不一定是员工。你可以选择任何东西。它只是一个小部件。想象一下 Tbl1 可能包含非常非常大的数据集。确定的是有两个 table 具有主键/外键关系。)
这是示例数据集:
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;
所以,我还是想 return 将错误发送给调用者,即记录错误,但我不希望它停止执行存储过程。它应该继续非常类似于 try..catch..finally 块。这可以用 THROW
来完成,还是我必须使用其他方法?
也许我弄错了,但 THROW
不是 RAISERROR
的升级版本吗?今后,我们应该使用前者来处理异常吗?
我过去曾在这些情况下使用过 RAISERROR
,它非常适合我。但 THROW
是一个更简单、更优雅的解决方案,imo,并且可能是未来更好的实践。我不太确定。
提前感谢您的帮助。
What's set in stone is there are two tables which have a primary key / foreign key relationship.
在内部事务中使用 THROW 不是做你想做的事情的方法。从您的代码来看,您想要插入一个新员工,除非该员工已经存在,然后,无论该员工是否已经存在,您都希望在第二次插入子项时使用该员工的 PK/id table.
一种方法是拆分逻辑。这是我的意思的伪代码:
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的最后,如果变量是填充,然后 throw/raise 一个错误。