在生产服务器上发生了一些脚射。我解决了这个问题,但我现在很困惑。
有一部分存储过程在失败时保留状态。也就是说,如果过程引发错误,它无论如何都可以安全地提交。这是用save transaction
.
现在,我最初的理解是,您为事务设置的保存点名称对于使用它的存储过程来说是本地的。而在嵌套场景中,如果内部存储过程同名保存,其实是不同的名字,一切正常。
为了确保我的这种理解是正确的,我设置了一个测试用例:
create table dbo.[footest] (
v varchar(50) NULL
);
go
insert into dbo.footest(v) values ('Nothing'); go
create procedure dbo.[foo_test_tran_inner]
as
begin
set nocount on;
declare @foo int;
begin tran;
update dbo.footest set v = 'Inner, before savepoint';
save tran the_constant_name;
begin try
update dbo.footest
set v = 'Inner, after savepoint';
set @foo = 1/0;
end try
begin catch
rollback tran the_constant_name;
end catch;
commit tran;
set @foo = 1/0;
return 0;
end;
go
create procedure dbo.[foo_test_tran_outer]
as
begin
set nocount on;
begin tran;
update dbo.footest set v = 'Outer, before savepoint';
save tran the_constant_name;
begin try
update dbo.footest
set v = 'Outer, after savepoint';
exec dbo.foo_test_tran_inner;
end try
begin catch
rollback tran the_constant_name;
end catch;
commit tran;
return 0;
end;
go
begin tran;
exec dbo.foo_test_tran_outer;
commit tran;
select * from dbo.footest;
这会产生“外部,在保存点之前”。这意味着,保存点名称是过程的本地名称,并且在嵌套场景中被正确回滚。
在生产中,那些保持状态的存储过程正是这种模式。其中有更多代码,但如果你删除它,只留下保存、回滚和提交,它将完全是上面显示的内容。
但。它在生产中不起作用。相反,每个具有相同名称的嵌套保存点似乎都覆盖了先前的保存点,该保存点由调用过程创建。而当最外层的代码在收到异常后执行 a commit
(相信内部回滚已正确完成)时,数据库处于半螺旋状态。如果应用于上面的示例,这将意味着选择返回Inner, before savepoint
。
我使用调试器逐步完成了生产过程中的每一步,并确认执行正确地完成了所有预期save tran a_name
的和rollback tran a_name
.
为了解决生产中的问题,我替换了这个:
save tran the_constant_name;
...
rollback tran the_constant_name;
有了这个:
declare @savepoint varchar(32) = replace(newid(), '-', '');
save tran @savepoint;
...
rollback tran @savepoint;
并立即修复它。
那么给了什么?保存点名称是否是存储过程的本地名称?
如果是,那么为什么生产代码做了它所做的事情,并且如图所示成功修复了?
如果不是,那么为什么上面的测试示例会这样做呢?
不,它们不是存储过程的本地。
如果您在具有简单恢复模型的数据库中运行以下命令
它给出了这些结果
事务
[Savepoint Name]
日志中记录的不包含任何与存储过程相关的唯一标识符。如果您删除
rollback tran the_constant_name;
fromfoo_test_tran_inner
,则最终选择的结果将更改为Inner, before savepoint
显示rollback tran the_constant_name;
执行的 fromfoo_test_tran_outer
只是回滚到该名称的最新(未回滚)保存点,并且不考虑存储过程嵌套。