TSQL 在事务中尝试/捕获,反之亦然?
Posted
技术标签:
【中文标题】TSQL 在事务中尝试/捕获,反之亦然?【英文标题】:TSQL Try / Catch within Transaction or vice versa? 【发布时间】:2014-05-28 05:29:30 【问题描述】:我正在编写一个脚本,该脚本将从多个表中删除记录,但在删除之前它必须返回一个计数以供用户在提交前确认。
这是脚本的摘要。
BEGIN TRANSACTION SCHEDULEDELETE
BEGIN TRY
DELETE -- delete commands full SQL cut out
DELETE -- delete commands full SQL cut out
DELETE -- delete commands full SQL cut out
PRINT 'X rows deleted. Please commit or rollback.' --calculation cut out.
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage
ROLLBACK TRANSACTION SCHEDULEDELETE
PRINT 'Error detected, all changes reversed.'
END CATCH
--COMMIT TRANSACTION SCHEDULEDELETE --Run this if count correct.
--ROLLBACK TRANSACTION SCHEDULEDELETE --Run this if there is any doubt whatsoever.
这是我第一次编写事务,将 TRY/CATCH 块放在事务中是否正确/最佳做法,还是应该将事务放在 TRY 块中?
此脚本中的重要因素是用户必须手动提交事务。
【问题讨论】:
是的,在 try/catch 块之外。 永远不要等待最终用户提交事务,除非它是单用户模式数据库。 @dean 有问题吗?基本上,用户将被告知他们应该期望删除 X 条记录,如果数量匹配,则立即提交。事务运行时不会写入这些表(尽管其他表会写入)。 我赞同@dean 所说的话。除非您的用户在这类事情上有一定的经验,否则您将陷入困境。即便如此,用户错误迟早会发生。如果可能的话,最好只填补您拥有的任何信息空白,从而防止您首先创建可靠的脚本。如果绝对有必要在提交之前确认结果,那么我建议默认情况下将脚本置于回滚状态。但即使这样也不是很好的做法。 如果他们事先知道受影响的记录数应该是多少,那么最好在匹配时使用参数提交(并且没有发现错误),并在不匹配时使用原因和输出回滚。从长远来看,最好为用户错误和临时疏忽留出尽可能少的空间,您可以更好地自动化这些事情,它们往往越可靠。 【参考方案1】:除了上述 M.Ali 和 Dean 的好建议之外,对于那些希望在 SQL SERVER 中使用新的(er)TRY CATCH THROW 范式的人提供一点帮助:
(我找不到完整的语法,所以在这里添加它)
要点:HERE
此处的示例存储过程代码(来自我的要点):
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROC [dbo].[pr_ins_test]
@CompanyID INT
AS
SET NOCOUNT ON
BEGIN
DECLARE @PreviousConfigID INT
BEGIN TRY
BEGIN TRANSACTION MYTRAN; -- Give the transaction a name
SELECT 1/0 -- Generates divide by zero error causing control to jump into catch
PRINT '>> COMMITING'
COMMIT TRANSACTION MYTRAN;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
BEGIN
PRINT '>> ROLLING BACK'
ROLLBACK TRANSACTION MYTRAN; -- The semi-colon is required (at least in SQL 2012)
END; -- I had to put a semicolon to avoid error near THROW
THROW
END CATCH
END
【讨论】:
只是一个观察 - THROW 应该在 BEGIN/END 之外,因此无论事务是否打开,它都会抛出。 @MarkG 不确定我是否理解,如果 THROW 在 BEGIN/END Catch 之外,则执行此存储过程总是会引发异常,除非您在 commit tran 之后使用 return 语句。就像现在的摘录一样,只是将球移到外面是没有用的 我怀疑@MarkG 是说 THROW 应该在 END 之后但在 END CATCH 之前。这将确保无论@@TRANCOUNT 是什么都抛出异常。 我尝试在 Visual Studio 的数据库项目中添加 THROW 语句,但出现错误。 我处理与THROW
相关的分号的方式是我只写;THROW;
——不管怎样。我只是喜欢这样做,所以我不会忘记添加它。【参考方案2】:
只有在您位于 TRY
块内且就在实际语句之前,才打开事务,并立即提交。不要等待您的控件进入批处理的末尾来提交您的事务。
如果您在TRY
块中出现问题并且您已经打开了事务,则控件将跳转到CATCH
块。只需在此处回滚您的事务并根据需要进行其他错误处理。
在实际回滚事务之前,我使用@@TRANCOUNT
函数对任何打开的事务添加了一点检查。在这种情况下,它真的没有多大意义。当您在打开事务之前在 TRY
块中进行一些验证检查时,它会更有用,例如检查参数值和其他内容,如果任何验证检查失败,则会在 TRY
块中引发错误。在这种情况下,控件将跳转到CATCH
块,甚至无需打开事务。在那里,您可以检查任何打开的事务,如果有任何打开的事务则回滚。在您的情况下,您确实不需要检查任何未结交易,因为除非您的交易出现问题,否则您不会进入CATCH
块。
在你执行完DELETE
操作后不要询问它是否需要提交或回滚;在打开交易之前进行所有这些验证。事务打开后,立即提交,如果出现任何错误,请进行错误处理(通过使用几乎所有错误函数获取详细信息,您做得很好)。
BEGIN TRY
BEGIN TRANSACTION SCHEDULEDELETE
DELETE -- delete commands full SQL cut out
DELETE -- delete commands full SQL cut out
DELETE -- delete commands full SQL cut out
COMMIT TRANSACTION SCHEDULEDELETE
PRINT 'X rows deleted. Operation Successful Tara.' --calculation cut out.
END TRY
BEGIN CATCH
IF (@@TRANCOUNT > 0)
BEGIN
ROLLBACK TRANSACTION SCHEDULEDELETE
PRINT 'Error detected, all changes reversed'
END
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage
END CATCH
【讨论】:
我知道 SQL Server 支持事务已经有几十年了,但TRY/CATCH
是最近才添加的 - 那么在将 TRY/CATCH
添加到 SQL Server 之前应该如何实现 BEGIN TRANSACTION + COMMIT + ROLLBACK
呢?
完全没有理由在TRY
块中打开交易。事实上,在TRY
之前立即打开它是正确的结构。
@Dai TRY/CATCH
自 2005 年以来一直在 SQL Server 中,而在 SQL Server 2005(即 SQL Sever 2000 和更早版本)之前,有很多我们不能做的事情,或者非常复杂做最简单的事情的方法。所以我希望你不在 SQL Server 2000 上:)。让您知道,在 2005 年之前,我们在每个语句后使用 @@Error
函数来检查语句是否出错。
@Ant_222 你想扩展你的要求吗?我已经详细说明了我提出的主张的原因。您刚刚发表了声明,但没有提供任何理由说明您建议的方法是正确的方法。
将BEGIN TRAN
保留在外部 TRY
块的原因很简单:如果BEGIN TRAN
失败,则不需要ROLLBACK
。现在,我想听听您将BEGIN TRAN
inside 放在TRY
块中的原因(我在您的回答中没有看到)。 BEGIN TRAN
外面可能有什么问题?【参考方案3】:
永远不要等待最终用户提交事务,除非它是单用户模式数据库。
简而言之,它是关于阻塞的。您的事务将对正在更新的资源采取一些排他锁,并将持有这些锁直到事务结束(提交或回滚)。没有人能够触摸这些行。如果将快照隔离与版本存储清理一起使用,则会出现一些不同的问题。
最好先发出一个选择查询来确定一些符合条件的行,将其呈现给最终用户,然后在他确认后执行实际删除。
【讨论】:
这是正确的解决方案。但请务必记住符合删除条件的行并尽可能删除它们,而不是在用户确认操作时发生任何事情。以上是关于TSQL 在事务中尝试/捕获,反之亦然?的主要内容,如果未能解决你的问题,请参考以下文章
尝试 开始事务,保存更改 捕获 回滚,开始另一个事务,保存更改 EF 核心