0

我有两个类似的 TSQL 脚本,其中我设置了 XACT_ABORT ON。我希望_test表在脚本执行后不存在,因为我故意使用包装在事务中的语句会引发错误。

第一个脚本正确回滚 _test 表的创建。

SET XACT_ABORT ON
GO
BEGIN TRANSACTION
CREATE TABLE dbo._test(ID int IDENTITY(1, 1) NOT NULL)
EXEC sp_does_not_exist --raises error!
GO
IF @@TRANCOUNT > 0
    COMMIT;

但是,第二个脚本不会回滚 _test 的创建,并且该表在脚本执行后存在:

SET XACT_ABORT ON
GO
BEGIN TRANSACTION
CREATE TABLE dbo._test(ID int IDENTITY(1, 1) NOT NULL)
EXEC sp_recompile sp_does_not_exist; --raises error!
GO
IF @@TRANCOUNT > 0
    COMMIT;

为什么第二个脚本在执行后没有删除 _test 表?

4

1 回答 1

0

您没有提到您的 SQL Server 版本,但由于您的脚本的唯一区别是sp_recompile,这似乎是一个很好的查看位置。在 2008R2 中,它具有以下逻辑:

BEGIN TRANSACTION
-- CHECK VALIDITY OF OBJECT NAME --
--  (1) Must exist in current database
--  (2) Must be a table or an executable object
select @objid = object_id(@objname, 'local')

    if @objid is null OR
        (ObjectProperty(@objid, 'IsTable') = 0 AND
         ObjectProperty(@objid, 'IsExecuted') = 0)
    begin
        COMMIT TRANSACTION
        raiserror(15165,-1,-1 ,@objname)
        return @@error
    end

因此,在尝试直接访问对象之前sp_recompile检查对象是否存在,如果未找到,则会引发严重性为 -1 的错误。文档说明严重性级别小于零被解释为零,严重性级别文档说明严重性为零时不会引发系统错误。RAISERROR

实际上,将RAISERRORfrom添加sp_recompile到您的脚本中表明它不会影响@@TRANCOUNT

SET XACT_ABORT ON
GO
BEGIN TRANSACTION
CREATE TABLE dbo._test(ID int IDENTITY(1, 1) NOT NULL)
select @@trancount as 'before raiserror'    
raiserror(15165,-1,-1 ,'sp_does_not_exist')
select @@trancount as 'after raiserror' 

GO
IF @@TRANCOUNT > 0
    COMMIT;

@@TRANCOUNT在引发错误之前和之后为 1,因此没有什么可以触发回滚。但是如果你这样做,第二个SELECT永远不会执行,因为数据库引擎“直接”引发了错误:

SET XACT_ABORT ON
GO
BEGIN TRANSACTION
CREATE TABLE dbo._test(ID int IDENTITY(1, 1) NOT NULL)
select @@trancount as 'before raiserror'    
exec sp_does_not_exist
select @@trancount as 'after raiserror' 

GO
IF @@TRANCOUNT > 0
    COMMIT;
于 2012-11-30T21:34:47.533 回答