我有一个很长的 SQL 脚本,我通过在 SSMS 中逐个语句执行它来测试它。我正在本地机器上的数据库上进行测试。
脚本中没有“外部”事务或嵌套事务。
精简示例:
USE NameOfDatabase
-- rename a table (outside a transaction)
EXEC sp_rename 'Table_XYZ', 'TableZ';
-- add a column to a table (outside a transaction)
ALTER TABLE TableB
ADD NewCol {...}
根据输出选项卡,所有这些语句都成功执行。
在脚本的很多地方,我都使用了 BEGIN TRANSACTION
/COMMIT TRANSACTION
,中间只有几条语句。所有这些似乎都按预期工作,因为我在执行 Command(s) completed successfully
语句时收到消息“COMMIT TRANSACTION
”。
示例:
-- create table statement outside a transaction
CREATE TABLE NewTableC {...}
-- inserting data from a table to be dropped, and dropping the table
BEGIN TRANSACTION
INSERT INTO NewTableC {...}
SELECT {...} FROM ObsoleteTableC
DROP TABLE ObsoleteTableC
COMMIT TRANSACTION -- "Command(s) completed successfully"
-- another smaller transaction
BEGIN TRANSACTION
-- Table HasFK_A contains the foreign key constraint 'FK_A_ToBigTable'
DROP TABLE HasFK_A
COMMIT TRANSACTION -- "Command(s) completed successfully"
然后我的脚本中有一个特别大的事务,我在其中创建一个新表、插入数据、删除约束、删除表等。
示例:
BEGIN TRANSACTION
-- create replacement table with temporary name
CREATE TABLE Tmp_BigTable {...}
-- insert data
INSERT INTO Tmp_BigTable {...}
SELECT {...} FROM BigTable
-- drop constraints to old table
-- Table 'HasFK_A' and its constraint 'FK_A_ToBigTable' should've already been dropped in a previous transaction
ALTER TABLE HasFK_B DROP CONSTRANT FK_B_ToBigTable
ALTER TABLE HasFK_C DROP CONSTRANT FK_C_ToBigTable
-- drop old table
DROP TABLE BigTable -- error, there are still foreign keys referencing the table
-- rename new table
EXEC sp_rename 'Tmp_BigTable', 'BigTable'
-- re-add constraints
ALTER TABLE HasFK_B DROP CONSTRANT FK_B_ToBigTable
ALTER TABLE HasFK_C DROP CONSTRANT FK_C_ToBigTable
-- commit transaction
COMMIT TRANSACTION
当我在那个更大的事务中执行 DROP TABLE BigTable
语句时,我收到一个错误,指出仍然有一个引用表的外键。
我打开了一个新的查询窗口并检查了 sys.foreign_keys
以查看我遗漏了什么。令人惊讶的是,我在之前的较小事务中已经删除或重命名的表中列出了外键,这些表似乎已成功提交。我很快为所有剩余的外键编写了 ALTER TABLE
语句,并执行了它们。他们都没有出错,我认为这意味着那些“删除”和“重命名”的表仍然存在于数据库中。
我检查了 DROP TABLE BigTable
语句现在是否会运行,因为我删除了额外的外键。它再次失败 - sys.foreign_keys
中的其余键现在是我最初在脚本中拥有的键 (FK_B_ToBigTable
, FK_C_ToBigTable
)。这让我相信第一个错误是事务回滚时。
我删除了这些约束,然后是 BigTable
,以确保已捕获所有外键。这一次,BigTable
掉线成功。我确实尝试通过重命名不存在的新表 (Tmp_BigTable
) 并提交事务来确认事务已回滚,但由于没有打开的事务而失败。
令人困惑的是,事务似乎回滚到了“大”事务的范围之外。被删除的表似乎仍然存在,被重命名的表似乎有它们的旧名称。
我的问题是:
每个MSDN:
<块引用>如果批处理中出现运行时语句错误(例如违反约束),数据库引擎中的默认行为是仅回滚生成错误的语句 。您可以使用 SET XACT_ABORT
语句更改此行为。
我的脚本中没有任何 SET XACT_ABORT
语句。
COMMIT
语句时回滚(或从未提交)显示“命令成功完成”的更改?编辑:我已经通过脚本找到了语句似乎已回滚到的点。看来我可能不小心忘记执行其中一个较小交易的 COMMIT
语句。
那么实际执行的内容是这样的:
BEGIN TRANSACTION
UPDATE ...
EXEC sp_rename ...
/* missed COMMIT TRANSACTION */
ALTER TABLE ...
UPDATE ...
CREATE TABLE ...
BEGIN TRANSACTION
INSERT ...
DROP TABLE ...
COMMIT TRANSACTION
BEGIN TRANSACTION
ALTER TABLE {NewName} DROP CONSTRANT ...
DROP TABLE ...
-- error on DROP TABLE rolls back both inner and outer nested transactions
ROLLBACK TRANSACTION
SELECT * FROM sys.foreign_keys -- returns foreign keys as they looked before "outer" transaction
这样就可以回答问题 2,而不是问题 1。