可序列化事务死锁

12

文档说,“可序列化”事务一个接一个地执行。

但实际情况似乎并不是这样。这里有两个几乎相同的事务,唯一的区别是延迟了15秒钟。

#1:

set transaction isolation level serializable
go
begin transaction
if not exists (select * from articles where title like 'qwe')
begin
waitfor delay '00:00:15'
insert into articles (title) values ('qwe')
end
commit transaction go
set transaction isolation level serializable
go
begin transaction
if not exists (select * from articles where title like 'qwe')
begin
insert into articles (title) values ('asd')
end
commit transaction go

第二个事务在第一个事务启动后几秒钟运行。

结果是死锁。第一个事务失败并结束。

Transaction (Process ID 58) was deadlocked on 
lock resources with another process and has been chosen as the deadlock victim. 
Rerun the transaction.

结论是,可序列化的事务并不是串行的?


1
在哪里说serializable意味着一次只能进行一个事务?据我所知,可序列化的事务隔离级别将在资源上获得独占/范围锁,即使它仅读取数据(在较不严格的隔离级别中,当仅读取数据时,它会获得共享锁),并防止其他用户甚至读取数据。 - M.Ali
1
Serializable 事务的整个目的是尽可能并行运行它们。事务的最终结果将与一个接一个地运行它们的结果相同,因此您得到的承诺是并行执行不会造成任何影响。如果您想在两个事务可以同时运行而不发生冲突的情况下一个接一个地运行,请改用显式锁定。在 serializable 隔离级别下,您冒着浪费 CPU 和 RAM 的风险,试图并行执行查询以减少典型延迟。 - Mikko Rantalainen
3个回答

11

这里发生了什么: 因为事务1在可串行化的隔离级别下运行,所以它保持了对表articles获取的共享锁,同时等待。这样,它可以保证直到事务终止,不存在的条件仍然为真。 事务2也获取了一个共享锁,使其能够进行存在检查条件。然后,在插入语句中,事务2需要将共享锁转换为独占锁,但必须等待事务1持有的共享锁。 当事务1完成等待时,它还会请求转换为独占模式=>死锁情况,必须终止其中一个事务。


这正是我的问题。我所做的是使用显式独占锁进行SELECT,问题就解决了。 - Andrew Larsson
很好的问题,有很好的解释,还给出了一个例子,说明仅处理一张表也可能会产生死锁的情况。 - Vikash

11

可序列化事务不一定按顺序执行。

承诺仅在以下情况下允许事务提交,即其结果与串行执行(以任何顺序)的结果相同。

为了满足此保证的锁定要求通常会导致死锁,其中一个事务需要回滚。您需要编写自己的重试逻辑以重新提交失败的查询。

有关逻辑描述和实现之间差异的更多信息,请参见可串行化隔离级别


1
在高负载下,这可能会发生数千次,特别是如果有许多副本的查询都请求序列化访问。这使得该模式几乎无用,因为它一直处于死锁状态。最好的方法可能是提前获取一个独占表锁并保持它。然后查询至少都排队等待完成,而不会出现死锁错误。 - Triynko
如果您想采用这种方法,使用带有UPDLOCK提示的U锁比X锁更少阻塞。 - Martin Smith
@Triynko,你可能需要增加可用于行锁定的锁数量,以避免隔离级别为Serializable的事务之间发生冲突。如果PostgreSQL缺少锁,并且有多个事务尝试使用表级锁而不是行级锁,则会升级行级锁为表级锁,这些事务显然会发生冲突。 - Mikko Rantalainen

5

我遇到了类似的问题,我发现:

MSDN

SERIALIZABLE 指定以下内容:

  • 语句不能读取其他事务修改但尚未提交的数据。
  • 在当前事务完成之前,没有其他事务可以修改当前事务读取的数据。
  • 其他事务不能插入键值范围落在当前事务中任何语句读取的键之间的新行,直到当前事务完成。

第二点并没有说明两个会话都不能获取共享锁,否则会导致死锁。我们通过 SELECT 的提示解决了这个问题。
select * from articles WITH (UPDLOCK, ROWLOCK) where title like 'qwe'

我不确定这种情况下它是否有效,但我认为您需要锁定表格部分,因为行尚未创建。


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