TRANSACTION回滚无法按预期工作

时间:2017-04-03 12:54:37

标签: sql-server transactions

我正在做一些数据库架构重组。

我的脚本大致如下:

BEGIN TRAN LabelledTransaction
    --Remove FKs
    ALTER TABLE myOtherTable1 DROP CONSTRAINT <constraintStuff>
    ALTER TABLE myOtherTable2 DROP CONSTRAINT <constraintStuff>

    --Remove PK
    ALTER TABLE myTable DROP CONSTRAINT PK_for_myTable

    --Add replacement id column with new type and IDENTITY
    ALTER TABLE myTable ADD id_new int Identity(1, 1) NOT NULL
    GO
    ALTER TABLE myTable ADD CONSTRAINT PK_for_myTable PRIMARY KEY CLUSTERED (id_new)
    GO
    SELECT * FROM myTable

    --Change referencing table types
    ALTER TABLE myOtherTable1 ALTER COLUMN col_id int NULL
    ALTER TABLE myOtherTable2 ALTER COLUMN col_id int NOT NULL

    --Change referencing table values
    UPDATE myOtherTable1 SET consignment_id = Target.id_new FROM myOtherTable1 AS Source JOIN <on key table>
    UPDATE myOtherTable2 SET consignment_id = Target.id_new FROM myOtherTable2 AS Source JOIN <on key table>

    --Replace old column with new column 
    ALTER TABLE myTable DROP COLUMN col_id
    GO
    EXEC sp_rename 'myTable.id_new', 'col_id', 'Column'
    GO

    --Reinstate any OTHER PKs disabled
    ALTER TABLE myTable ADD CONSTRAINT <PK defn>

    --Reinstate FKs
    ALTER TABLE myOtherTable1 WITH CHECK ADD CONSTRAINT <constraintStuff>
    ALTER TABLE myOtherTable2 WITH CHECK ADD CONSTRAINT <constraintStuff>   

    SELECT * FROM myTable

    -- Reload out-of-date views
    EXEC sp_refreshview 'someView'

    -- Remove obsolete sequence
    DROP SEQUENCE mySeq
ROLLBACK TRAN LabelledTransaction

显然,所有这些都有所修改,但细节并不是重要的事情。

当然,在核心更改之前找到所有需要关闭/编辑的东西是很困难的(即使有一些元查询可以帮助我),所以我并不总是第一次得到正确的脚本。

但我放入ROLLBACK以确保失败的尝试使DB保持不变。

但我实际看到的是,如果TRAN中存在错误,则不会发生ROLLBACK。我想我得到的错误是“没有匹配TRAN回滚”?

我的第一直觉是关于GO陈述,但是https://stackoverflow.com/a/11121382/1662268建议标记TRAN应该已经修复了吗? 发生了什么?如果有错误,为什么不能正确回滚更改。

如何以这样的方式编写和测试这些脚本:如果脚本第一次不完美,我不必手动还原任何部分更改?

修改 基于第一个答案的附加评论。

如果链接的答案不适用于此查询,您是否可以扩展其原因,以及为什么它与答案中的示例不同?

我不能(或者说,我相信我不能)删除GO,因为上面的脚本需要GO s才能编译。如果我删除GO s,那么后来依赖于新添加/重命名的列的语句不会编译。并且查询无法运行。

有没有办法解决这个问题,删除GO

3 个答案:

答案 0 :(得分:4)

如果您有任何错误会自动导致事务回滚,那么事务将作为当前批次的一部分回滚。

然后,控制将返回到客户端工具,然后将下一批次发送到服务器,下一批(以及后续批处理)将不会包装在任何事务中。

最后,当执行最后一批尝试运行回滚的批处理时,您将收到收到的错误消息。

因此,当不受事务保护时,您需要保护每个批次的运行。

一种方法是插入旧的GOTO

GO
IF @@TRANCOUNT=0 GOTO NBATCH
...Rest of Code
NBATCH:
GO

SET FMTONLY

GO
IF @@TRANCOUNT=0 BEGIN
    SET FMTONLY ON
END
...Rest of Code
GO

当然,这不会解决所有问题 - 某些语句需要是批处理中的第一个或唯一的语句。要解决这些问题,我们必须将上述技术之一与某种形式的EXEC结合使用:

GO
IF @@TRANCOUNT=0 BEGIN
    SET FMTONLY ON
END
EXEC sp_executesql N'/*Code that needs to be in its own batch*/'
GO

(如果一批代码依赖于先前批处理已执行的工作而引入 new 数据库对象(表,列等),则还必须使用此技术,因为如果之前的那个批量永不执行,新对象将不存在)

我刚刚发现sqlcmd tool存在-b选项。以下脚本在通过SSMS运行时会生成两个错误:

begin transaction
go
set xact_abort on
go
create table T(ID int not null,constraint CK_ID check (ID=4))
go
insert into T(ID) values (3)
go
rollback

错误:

Msg 547, Level 16, State 0, Line 7
The INSERT statement conflicted with the CHECK constraint "CK_ID". The conflict occurred in database "TestDB", table "dbo.T", column 'ID'.
Msg 3903, Level 16, State 1, Line 9
The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.

但是,相同的脚本保存为Abortable.sql并使用以下命令行运行:

sqlcmd -b -E -i Abortable.sql -S .\SQL2014 -d TestDB

生成单个错误:

Msg 547, Level 16, State 1, Server .\SQL2014, Line 1
The INSERT statement conflicted with the CHECK constraint "CK_ID". The conflict
occurred in database "TestDB", table "dbo.T", column 'ID'.

因此,看起来从命令行运行脚本并使用-b选项可能是另一种方法。我刚刚搜索了SSMS选项/属性,看看我是否能找到与-b相当的东西但是我找不到它。

答案 1 :(得分:-2)

删除完成交易的“GO”

答案 2 :(得分:-2)

如果完成只有ROLLBACK - 只需使用TRY / CATCH:

BEGIN TRANSACTION;
BEGIN TRY
     --Remove FKs
    ALTER TABLE myOtherTable1 DROP CONSTRAINT <constraintStuff>
    ALTER TABLE myOtherTable2 DROP CONSTRAINT <constraintStuff>
    --Remove PK
    ALTER TABLE myTable DROP CONSTRAINT PK_for_myTable
    --Add replacement id column with new type and IDENTITY
    ALTER TABLE myTable ADD id_new int Identity(1, 1) NOT NULL
    ALTER TABLE myTable ADD CONSTRAINT PK_for_myTable PRIMARY KEY CLUSTERED (id_new)
    SELECT * FROM myTable
    --Change referencing table types
    ALTER TABLE myOtherTable1 ALTER COLUMN col_id int NULL
    ALTER TABLE myOtherTable2 ALTER COLUMN col_id int NOT NULL
    --Change referencing table values
    UPDATE myOtherTable1 SET consignment_id = Target.id_new FROM myOtherTable1 AS Source JOIN <on key table>
    UPDATE myOtherTable2 SET consignment_id = Target.id_new FROM myOtherTable2 AS Source JOIN <on key table>
    --Replace old column with new column 
    ALTER TABLE myTable DROP COLUMN col_id
    EXEC sp_rename 'myTable.id_new', 'col_id', 'Column'
    --Reinstate any OTHER PKs disabled
    ALTER TABLE myTable ADD CONSTRAINT <PK defn>
    --Reinstate FKs
    ALTER TABLE myOtherTable1 WITH CHECK ADD CONSTRAINT <constraintStuff>
    ALTER TABLE myOtherTable2 WITH CHECK ADD CONSTRAINT <constraintStuff>   
    SELECT * FROM myTable
    -- Reload out-of-date views
    EXEC sp_refreshview 'someView'
    -- Remove obsolete sequence
    DROP SEQUENCE mySeq
    ROLLBACK TRANSACTION
END TRY
BEGIN CATCH
   print 'Error caught'
   select ERROR_NUMBER() AS ErrorNumber, ERROR_MESSAGE() AS ErrorMessage;
END CATCH;