XACT_ABORT与批处理中的sp_recompile行为不一致

时间:2012-11-30 18:46:15

标签: sql-server tsql

我有两个类似的TSQL脚本,其中我有SET 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表?

1 个答案:

答案 0 :(得分: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 states文档被解释为零,严重级别states的文档在严重性为零时不会引发系统错误。

确实,将RAISERROR 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;