use*_*342 27 sql-server stored-procedures transaction
我有一个存储过程,它只在其中执行 3 个存储过程。如果主 SP 成功,我只使用 1 个参数来存储。
如果第一个存储过程在主存储过程中工作正常,但第二个存储过程失败,那么它会自动回滚主 SP 中的所有 SP 还是我必须发出一些命令?
这是我的程序:
CREATE PROCEDURE [dbo].[spSavesomename]
-- Add the parameters for the stored procedure here
@successful bit = null output
AS
BEGIN
begin transaction createSavebillinginvoice
begin Try
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
BEGIN
EXEC [dbo].[spNewBilling1]
END
BEGIN
EXEC [dbo].[spNewBilling2]
END
BEGIN
EXEC [dbo].[spNewBilling3]
END
set @successful = 1
end Try
begin Catch
rollback transaction createSavesomename
insert into dbo.tblErrorMessage(spName, errorMessage, systemDate)
values ('spSavesomename', ERROR_MESSAGE(), getdate())
return
end Catch
commit transaction createSavesomename
return
END
GO
Run Code Online (Sandbox Code Playgroud)
Sol*_*zky 62
鉴于只有在问题中显示的代码,并假设没有这三个子特效有任何明确的事务处理,那么,在任何三个子特效的错误将被捕获,并且ROLLBACK在CATCH块将回滚所有的工作。
但是这里有一些关于事务的注意事项(至少在 SQL Server 中):
无论您调用多少次,都只有一次真正的交易(第一次)BEGIN TRAN
BEGIN TRAN,无论是否命名,事务计数器都会增加 1。SELECT @@TRANCOUNT;COMMIT时发出的任何命令只会@@TRANCOUNT减少事务计数器,一次一个。COMMIT发出时,@@TRANCOUNT在1保存点允许在事务中创建可以撤消的工作子集。
SAVE TRAN {save_point_name}命令创建/标记保存点ROLLBACK {save_point_name}。(更多关于这个下面)SAVE TRAN {save_point_name},包括在创建回滚的保存点之后创建的任何保存点(因此是“嵌套”)。SAVE TRAN除非发出完整ROLLBACK的整个交易,否则在初始之前完成的任何工作都无法撤消。COMMIT时发出when@@TRANCOUNT对保存点没有影响(因为同样,在该计数器之外不存在高于 1 的事务级别)。您不能提交特定的命名事务。事务“名称”(如果与 一起提供)将COMMIT被忽略并且仅存在于可读性上。
ROLLBACK没有名称的发出将始终回滚所有事务。
ROLLBACK带有名称的发行必须对应于:
SAVE TRAN使用相同的事务名称调用,这将回滚所有事务。SAVE TRAN {save_point_name}调用以来所做的所有更改。SAVE TRAN发出以其名称发出的命令,则该事务名称的每个 ROLLBACK 将撤消每个保存点,直到该名称不再存在。之后,以该名称发出的 ROLLBACK 将回滚所有事务。例如,假设以下命令按所示顺序运行:
BEGIN TRAN A -- @@TRANCOUNT is now 1
-- DML Query 1
SAVE TRAN A
-- DML Query 2
SAVE TRAN A
-- DML Query 3
BEGIN TRAN B -- @@TRANCOUNT is now 2
SAVE TRAN B
-- DML Query 4
Run Code Online (Sandbox Code Playgroud)
现在,如果您发出(以下每种情况相互独立):
ROLLBACK TRAN B一次:它将撤消“DML 查询 4”。@@TRANCOUNT还是2。ROLLBACK TRAN B两次:它将撤消“DML 查询 4”,然后错误,因为“B”没有相应的保存点。@@TRANCOUNT还是2。ROLLBACK TRAN A一次:它将撤消“DML 查询 4”和“DML 查询 3”。@@TRANCOUNT还是2。ROLLBACK TRAN A两次:它将撤消“DML 查询 4”、“DML 查询 3”和“DML 查询 2”。@@TRANCOUNT还是2。ROLLBACK TRAN A三次:它将撤消“DML 查询 4”、“DML 查询 3”和“DML 查询 2”。然后它将回滚整个事务(剩下的就是“DML 查询 1”)。@@TRANCOUNT现在是 0。COMMIT一次:@@TRANCOUNT下降到 1。COMMIT一次ROLLBACK TRAN B又一次:@@TRANCOUNT下降到 1。然后它将撤消“DML 查询 4”(证明 COMMIT 没有做任何事情)。@@TRANCOUNT还是1。事务名称和保存点名称:
存储过程本身并不是隐式事务。如果未启动显式事务,则每个查询都是隐式事务。这就是为什么不需要围绕单个查询进行显式事务的原因,除非可以出于编程原因执行 a ROLLBACK,否则查询中的任何错误都是该查询的自动回滚。
调用存储过程时,它必须以与@@TRANCOUNT调用时相同的值退出。意思是,你不能:
BEGIN TRAN在 proc 中启动 a而不提交它,期望在调用/父进程中提交。ROLLBACK如果在调用 proc 之前启动了显式事务,则不能发出 a ,因为它将返回@@TRANCOUNT0。如果退出存储过程时事务计数高于或低于启动时,您将收到类似于以下内容的错误:
消息 266,级别 16,状态 2,过程 YourProcName,行 0
EXECUTE 后的事务计数表示 BEGIN 和 COMMIT 语句的数量不匹配。先前计数 = X,当前计数 = Y。
表变量,就像常规变量一样,不受事务的约束。
关于在 procs 中进行事务处理,可以独立调用(因此需要事务处理)或从其他 procs 调用(因此不需要事务处理):这可以通过几种不同的方式完成。
几年来我一直在处理它似乎运作良好的方式是只BEGIN/ COMMIT/ROLLBACK在最外层。子过程调用只是跳过事务命令。我在下面概述了我放入每个 proc 的内容(嗯,每个都需要事务处理)。
DECLARE @InNestedTransaction BIT;代替 simple BEGIN TRAN,请执行以下操作:
IF (@@TRANCOUNT = 0)
BEGIN
SET @InNestedTransaction = 0;
BEGIN TRAN; -- only start a transaction if not already in one
END;
ELSE
BEGIN
SET @InNestedTransaction = 1;
END;
Run Code Online (Sandbox Code Playgroud)代替 simple COMMIT,请执行以下操作:
IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
BEGIN
COMMIT;
END;
Run Code Online (Sandbox Code Playgroud)代替 simple ROLLBACK,请执行以下操作:
IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
BEGIN
ROLLBACK;
END;
Run Code Online (Sandbox Code Playgroud)无论事务是在 SQL Server 内启动还是在应用程序层启动,此方法都应该以相同的方式工作。
有关TRY...CATCH构造中此事务处理的完整模板,请参阅我对以下 DBA.SE 问题的回答:我们是否需要在 C# 代码以及存储过程中处理事务。
除了“基础知识”之外,还有一些交易的细微差别需要注意:
默认情况下,事务在大多数情况下不会在发生错误时自动回滚/取消。只要您有适当的错误处理并给ROLLBACK自己打电话,这通常不是问题。但是,有时事情会变得复杂,例如在批处理中止错误的情况下,或者在使用OPENQUERY(或通常的链接服务器)时,远程系统上发生错误。虽然大多数错误都可以使用 捕获TRY...CATCH,但有两个错误无法通过这种方式捕获(虽然现在不记得是哪个——正在研究中)。在这些情况下,您必须使用SET XACT_ABORT ON来正确回滚事务。
SET XACT_ABORT ON使 SQL Server立即回滚任何事务(如果一个事务处于活动状态)并在发生任何错误时中止批处理。此设置在引入TRY...CATCH构造的SQL Server 2005 之前就已存在。在TRY...CATCH大多数情况下,可以处理大多数情况,因此大多数情况下都不再需要XACT_ABORT ON. 但是,在使用时OPENQUERY(可能还有我目前不记得的另一种情况),那么您仍然需要使用SET XACT_ABORT ON;.
在触发器内部,XACT_ABORT隐式设置为ON。这会导致触发器中的任何错误取消触发触发器的整个 DML 语句。
您应该始终进行适当的错误处理,尤其是在使用事务时。TRY...CATCHSQL Server 2005 中引入的构造提供了一种处理几乎所有情况的方法,这是对@@ERROR每个语句之后的测试的可喜改进,这对批处理中止错误没有太大帮助。
TRY...CATCH然而,引入了一个新的“状态”。当不使用TRY...CATCH结构,如果你有一个活跃的交易并发生错误,那么还有一些可以采取几种途径:
XACT_ABORT OFF和语句中止错误:事务仍处于活动状态,处理继续执行下一个语句(如果有)。XACT_ABORT OFF和大多数批处理中止错误:事务仍处于活动状态,处理继续进行下一个批处理(如果有)。XACT_ABORT OFF和某些批处理中止错误:事务被回滚并且处理继续下一个批处理,如果有的话。XACT_ABORT ON和任何错误:事务被回滚,处理继续下一批,如果有的话。
然而,当使用 时TRY...CATCH,批处理中止错误不会中止批处理,而是将控制转移到CATCH块。如果XACT_ABORT是OFF,该交易仍活跃,绝大多数的时间,你会需要COMMIT,或最有可能的,ROLLBACK。但是当遇到某些批处理中止错误(例如 with OPENQUERY),或者当XACT_ABORTis 时ON,事务将处于新状态,“不可提交”。在这种状态下,您不能COMMIT,也不能进行任何 DML 操作。你所能做的就是ROLLBACK和SELECT陈述。然而,在这种“不可提交”的状态下,事务在发生错误时被回滚,发布ROLLBACK只是一种形式,但必须完成。
函数XACT_STATE可用于确定事务是活动的、不可提交的还是不存在的。建议(至少某些人)在CATCH块中检查此函数以确定结果是否-1(即不可提交)而不是测试 if @@TRANCOUNT > 0。但是对于XACT_ABORT ON,那应该是唯一可能的状态,因此似乎测试@@TRANCOUNT > 0和XACT_STATE() <> 0是等效的。另一方面,当XACT_ABORT是OFF并且有一个活动交易时,那么可能有一个状态1或-1在CATCH块中,这允许发行COMMIT而不是ROLLBACK(虽然,我想不出当有人会想要COMMIT如果事务是可提交的)。有关XACT_STATE()在CATCH块内使用的更多信息和研究XACT_ABORT ON可以在我对以下 DBA.SE 问题的回答中找到:当 XACT_ABORT 设置为 ON 时,在什么情况下可以从 CATCH 块内部提交事务?. 请注意,有一个小错误XACT_STATE()会导致它1在某些情况下错误地返回:XACT_STATE() 在 SELECT 中使用某些系统变量但没有 FROM 子句时返回 1
关于原始代码的说明:
BEGIN和END周围的每个EXEC通话是的,如果由于主存储过程的 catch 语句中的任何错误而执行回滚代码,它将回滚由任何直接语句或通过其中的任何嵌套存储过程执行的所有操作。
即使您没有在嵌套存储过程中应用任何显式事务,这些存储过程仍然会使用隐式事务,并将在完成时提交,但是您在嵌套存储过程中通过显式或隐式事务提交,SQL Server 引擎将忽略它,并将如果主存储过程失败并且事务回滚,则回滚这些嵌套存储过程的所有操作。
每次根据最外层事务结束时采取的操作来提交或回滚事务。如果提交了外部事务,则内部嵌套事务也会提交。如果回滚外部事务,则所有内部事务也会回滚,无论内部事务是否单独提交。
供参考http://technet.microsoft.com/en-us/library/ms189336(v=sql.105).aspx
| 归档时间: |
|
| 查看次数: |
46555 次 |
| 最近记录: |