我正在使用SQLServer 2014,我有一个简单的数据库,其中一个表有一个ID和一个名为data的varchar列。当我运行以下语句时,有一些奇怪的行为:
SET XACT_ABORT ON
BEGIN TRANSACTION
exec sp_executesql N'some nonsense'
insert into testTable values ('b')
COMMIT
SSMS显示存在错误,因为我试图在sp_executesql
调用中运行不正确的查询。但是,它也显示1 row(s) affected
。如果我在testTable上运行select查询,我可以看到插入了值'b'。
如果我将语句包装在TRY/CATCH
块中,一切都按预期工作,整个事务操作将回滚:
BEGIN TRANSACTION
BEGIN TRY
exec sp_executesql N'some nonsense'
insert into testTable values ('b')
COMMIT
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE()
ROLLBACK
END CATCH
如果出现问题,SET XACT_ABORT ON
不应该确保整个事务回滚吗?我缺少一个设置吗?
由于
答案 0 :(得分:4)
发生这种情况是因为TRY
/ CATCH
中未包含的运行时语法错误不会中止活动事务,即使XACT_ABORT
设置为ON
也是如此。什么做和不做中止的确切规则,以及在什么情况下,都不是直截了当或明显的。 Erland Sommarskog有an excellent write-up on error handling in general和the rules of what does and doesn't abort in particular。
我不会在这里重现所有内容,但这里的问题归结为其基本要素:
SET XACT_ABORT ON -- or OFF, it makes no difference
BEGIN TRANSACTION
EXEC ('SELECT') -- Incorrect syntax near 'SELECT'
PRINT @@TRANCOUNT -- Prints 1, transaction is still going
COMMIT
PRINT @@TRANCOUNT -- Prints 0, transaction succeeded
尽管XACT_ABORT ON
,执行不仅没有停止,交易甚至没有中止。添加TRY
/ CATCH
会更改规则:
SET XACT_ABORT ON
BEGIN TRANSACTION
BEGIN TRY
EXEC ('SELECT') -- Incorrect syntax near 'SELECT'
PRINT 'After bad statement.' -- Does not print
COMMIT
END TRY
BEGIN CATCH
PRINT @@TRANCOUNT -- Prints 1, transaction is still going, but it's doomed
END CATCH
-- Error here:
-- 'Uncommittable transaction is detected at the end of the batch.
-- The transaction is rolled back.'
现在交易注定失败了,如果我们不自己回滚,SQL Server会为我们做这件事(有错误)。这种彻头彻尾的礼貌完全由XACT_ABORT
提供,因为关闭它会产生不同的东西:
SET XACT_ABORT OFF
BEGIN TRANSACTION
BEGIN TRY
EXEC ('SELECT') -- Incorrect syntax near 'SELECT'
PRINT 'After bad statement.' -- Does not print
COMMIT
END TRY
BEGIN CATCH
PRINT @@TRANCOUNT -- Prints 1, transaction is still going
END CATCH
PRINT @@TRANCOUNT -- Prints 1, transaction is still going!
ROLLBACK
故事的寓意是:在T-SQL中正确的错误处理非常棘手。通常对我有用的是对任何非平凡的批处理语句执行SET XACT_ABORT ON
,并在SQL Server外部(通过客户端代码)启动和提交或回滚事务。这解决了理解什么做和不停止或毁坏事务的困难,因为SQL Server传递回客户端的任何错误最终都会导致回滚。但是,当然,即使这不是银弹。
答案 1 :(得分:0)
使用TRANSACTION for MSSQL的TRY ... CATCH
BEGIN TRANSACTION;
BEGIN TRY
-- Generate a constraint violation error.
SELECT 1/0
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;
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
END CATCH;
IF @@TRANCOUNT > 0
COMMIT TRANSACTION;
GO