为什么SQL Server死锁受害者错误(1205)会结束所有事务?

3

考虑以下两个表:

create table testDeadlockTable1 (
  c1   int not null,
  c2   int not null
)
create table testDeadlockTable2 (
  c1   int not null,
  c2   int not null
)

有了这些数据:

insert testDeadlockTable1 values (1, 1)
insert testDeadlockTable1 values (2, 2)
insert testDeadlockTable1 values (3, 3)
insert testDeadlockTable1 values (4, 4)

insert testDeadlockTable2 values (1, 1)
insert testDeadlockTable2 values (2, 2)
insert testDeadlockTable2 values (3, 3)
insert testDeadlockTable2 values (4, 4)

考虑以下两个存储过程:

create proc testDeadlockTestA
as
  begin tran

  update testDeadlockTable1
    set c1 = 3
    where c2 = 1

  waitfor delay '00:00:01' -- sleep 1 second

  select c1
    from testDeadlockTable2
    where c2 = 3

  commit tran
go
create proc testDeadlockTestB
as
  begin tran

  update testDeadlockTable2
    set c1 = 5
    where c2 = 2

  waitfor delay '00:00:01' -- sleep 1 second

  select c1
    from testDeadlockTable1
    where c2 = 4

  commit tran
go

在一个查询会话中,先调用testDeadlockTestA,再立即在另一个会话中调用testDeadlockTestB。后者会被选择为死锁牺牲品。
两个会话中,@@trancount在结束时都是0。
现在,在每个查询会话中都使用begin tran开始,这样当存储过程被调用时,@@trancount是1。因此,死锁应该发生在每个存储过程开始的事务内部(也就是内部交易)。
会话A未被选择为死锁牺牲品,其@@trancount为1,正如我们所期望的那样(我们没有结束外部事务)。但是,受害者会话B的@@trancount为0。
为什么死锁发生时内部和外部事务都结束了?是否有办法确保只在死锁情况下结束内部事务?
似乎死锁错误的行为就像XACT_ABORT被设置为开启一样(在这种情况下没有开启),因为在导致死锁的调用之后,任何其他查询语句都不会被执行。
这个问题的原因是想知道是否可以重新运行查询,如果发生死锁。如果它发生在一个更大的事务中,该事务旨在调用几个查询,则销毁外部事务意味着重新运行被死锁牺牲的查询是不安全的。但如果它只停止其直接环境,则是安全的。

你可能会发现这个很有趣。其中详细介绍了关于T-SQL中错误处理的几乎所有信息,包括它如何与事务交互。 - Jeroen Mostert
2个回答

6

SQL Server没有真正的嵌套事务。虽然有 SAVE TRANSACTION 和保存点名称等功能,有些情况下可能可以强制其工作类似于嵌套事务,但实际上并不是真正的嵌套事务1

因此,ROLLBACK 的行为(未尝试使用保存点)始终影响所有事务,无论嵌套级别如何。

而死锁破解程序强制执行的回滚从来没有机会指定保存点名称。


1值得注意的是,每个人都必须“知道玩笑”。您不能将现有的使用 BEGIN/ROLLBACK 的代码嵌套在事务中。它必须被重写为 SAVE TRANSACTION name/ROLLBACK name,现在它取决于调用代码始终将其包含在现有事务中。


1
当死锁发生时,为什么内部和外部事务都会结束?
你还有什么建议?编写一个可以解决每种可能情况的AI吗?快速失败是安全编程的已知原则。选择一个,终止它,完成 - 让程序员修复他们创造的混乱。
同时要理解,没有“内部事务”。只有一个事务 - 其他内部事务包含在外部事务中,因此必须回滚外部事务。

1
我没有更好的建议,这就是为什么我要问的原因。这种行为对我来说似乎是合理的,但我想要确定一下。我将不得不找到另一个解决方案来解决我的主要问题。 - Svip
1
@svip - 唯一的解决方案是确保您的所有事务都彼此线程安全(不能遇到死锁)。一个好的第一步是确保资源始终按相同顺序锁定,另一个好的步骤是尽可能使事务小而短暂(通过重新考虑逻辑,允许更早和更频繁地释放锁定)。 - MatBailie

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