SQL Server 中的 CREATE TABLE 语句是否免疫于 ROLLBACK?

9
我有一个数据库每周都会"发布"一次,也就是说我会发布一个备份供人们使用,并发布一个更新脚本用于从上周的版本升级(尽可能保留他们当前的数据)。这个脚本中当然包含了很多DDL - CREATE TABLE, ALTER TABLE等等。它的基本结构如下:
/*

HOW TO USE THIS SCRIPT

1.  Run it against your existing DB
2.  Check whether there were any errors
3.  If there were, issue a rollback by highlighting this:
        ROLLBACK
    and executing it
4.  If there weren't, issue a commit by highlighting this:
        COMMIT
    and executing it
5.  !!! Not doing either of these will leave a transaction open, which will
    probably cause all further queries to time out till you do !!!

*/

SET XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION;
GO

-- Boilerplate checking and whatnot goes here
GO

-- Guts of operation, part 1
GO

-- Guts of operation, part 2
GO

-- Guts of operation, part 3
GO

-- . . .
GO

-- Guts of operation, part N
GO

-- Boilerplate cleanup stuff here
GO

您会注意到,我保留了事务并告诉他们手动完成,具体取决于发生了什么。我宁愿这是自动的,但考虑到整个过程总是一系列长时间的批处理,而且TRY块当然不能跨越批处理。因此最近添加了SET XACT_ABORT ON;以稍微减轻痛苦。无论如何,自那时以来我自己尝试过没有这个选项,对情况没有影响。无论如何。
最近,其中一个脚本有创建表的语句和其他语句来向现有表添加检查约束。我的一个用户运行了该脚本,并在约束上遇到错误;结果他有违反约束的预先存在的数据。好的,没问题,执行ROLLBACK。对了,不需要执行ROLLBACK,XACT_ABORT已经执行了。去修复数据行...完成。现在再试一次!噢...什么?这次没有在约束上出错,但它在CREATE TABLE语句上出错,说表已经存在...!嗯?它没有回滚吗?
我们最终从备份中恢复并重新修复数据并重新运行。但这与此无关。
所以,亲爱的读者们:这些表的创建如何在事务回滚后幸存下来?我怎样才能使它们不幸存?
编辑:好的,这里有一个您可以运行的示例。
USE tempdb;
GO

CREATE DATABASE example;
GO

USE example;
GO

CREATE TABLE foo (a INT);
GO

INSERT INTO foo
VALUES (100);
GO

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION;
GO

ALTER TABLE foo ADD CHECK (a < 10);-- Gives error "The ALTER TABLE statement conflicted with the CHECK constraint…", as expected
GO

CREATE TABLE bar (b INT);
GO

ROLLBACK;-- Gives error "The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION."  Huh?  Where did our transaction go?
GO

SELECT *
FROM bar;-- Gives no error.  Table still exists!  NOT expected!
GO

USE tempdb;
GO

DROP DATABASE example;

不,它们并不免疫回滚,在您的情况下很难从描述中知道发生了什么。 - Martin Smith
尝试找到最简单的方法来重现问题,这将有助于我们分析问题。 - Andomar
1个回答

5
SQL Server有一个不幸的错误处理行为。根据特定的错误,有时会中止语句、批处理、事务和连接,或其中一些或无。这是一个非常容易出错的编程模型。
在您的情况下,由于XACT_ABORT,批处理和事务被中止。但GO是批处理分隔符,您有多个批处理。稍后的批次继续运行。
使用一个批次,或使后续批次检查前一个批次是否运行(也许通过检查@@TRANCOUNT)。
(对于SQL Server来说更好的模型是回滚事务但保持其打开状态并使所有未来的语句失败。这将使事务包含在脚本中,不会泄漏任何东西。SQL Server没有这样的功能。)

1
啊,原来是这样!SQLcmd 模式和 :on error exit 可能会有所帮助。 - Martin Smith
哎呀,我不能只用一个批处理,因为它无法通过语法检查,因为对象还不存在。例如,在CREATE TABLE和对结果表执行操作之间必须使用GO,否则它甚至不会尝试执行。而且我几乎没有希望向用户解释SQLCmd模式... :/ - Atario
@Atario 好的,你可以将所有内容都包裹在 try...catch 中,然后在 catch 块中引发连接中止错误或 set noexec on - Martin Smith

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接