在 SQL Server 2008 R2 中工作,我试图将一组 DDL 语句作为一个组回滚(想想数据库的升级脚本),但遇到了麻烦。
采取以下代码:
begin try
begin tran
create table foo (i int)
alter table foo add x dog
insert into foo select 1,1
insert into foo select 1,1,1
commit tran
end try
begin catch
rollback tran
print @@error
end catch
我希望尝试在 alter table 语句上失败,进入 catch,回滚事务,并打印错误消息。但是,如果您检查对象/表,您会看到 foo 仍然存在(因此创建表没有正确回滚)。
select * from sys.objects where name = 'foo'
我在这里做错了什么?
发生该错误是因为由于延迟名称解析而引发的错误是重新编译错误的一部分。查看 SQL BOL,当它们发生在与 try...catch 相同的级别时,它们并没有被困住。但是,如果它发生在不同的级别,无论是作为动态 SQL 还是作为 SP 调用,那么它将被捕获并回滚。
使用 Profiler,您可以看到“alter table foo add x dog”语句在执行之前重新编译,然后出错并绕过了 catch 块。
如果将语句包装在动态 SQL 中,则错误不会返回到 Profiler 并且事务会回滚
您看到此结果的原因是 SQL Server 实际上没有捕获您的 ALTER TABLE 错误。您会注意到,当您运行此程序时,您会看到红色错误消息而不是打印行——您可以通过更改
print @@error
为类似print 'HELLO!'
;来验证这一点。在这种情况下,您不会看到“你好!” 打印; 您将看到错误。Books Online列出了您无法捕捉到的错误案例。这里的另一种选择是
SET XACT_ABORT ON
在您开始交易之前。然后,您可以在遇到第一个错误时回滚更改。恕我直言,事情有点简单。不会调用您的 catch 块,因为您的代码永远不会执行。SQL Server 接收到一个文本块:
然后它试图理解它,因此它解析它,将它转换成一个句法树,但它仍然不能运行。为了使它可以运行,它需要编译它,在这个阶段它会爆炸并抛出异常。请求从未真正执行,因此您的 try/catch 块甚至没有启动。语法和编译错误与执行错误总是如此。您可以通过从客户端启动事务(例如将其包装在 TransactionScope 中)或通过在批处理中添加一个帧(即使用 sp_executesql)将事务和异常处理推送到堆栈上的一个帧。
但可惜这是一个失败的原因。进行一次事务迁移在实践中永远行不通。至少考虑所有不支持事务的 DDL,然后考虑当您发出数据大小的 DDL 时日志会发生什么(例如 ALTER TABLE ... ADD COLUMN ... WITH VALUES )。
进行备份,运行您的迁移,如果它从备份中恢复。是唯一可行的选择。