运行UPDATE时PostgreSQL中的死锁

vya*_*hir 28 postgresql transactions database-deadlocks

关于PostgreSQL死锁,我有点困惑.

典型的死锁示例是:

-- Transaction 1
UPDATE customer SET ... WHERE id = 1
UPDATE customer SET ... WHERE id = 2

-- Transaction 2
UPDATE customer SET ... WHERE id = 2
UPDATE customer SET ... WHERE id = 1
Run Code Online (Sandbox Code Playgroud)

但是,如果我更改代码如下:

-- Transaction 1
UPDATE customer SET ... WHERE id IN (1, 2)

-- Transaction 2
UPDATE customer SET ... WHERE id IN (1, 2)
Run Code Online (Sandbox Code Playgroud)

这可能会陷入僵局吗?

基本上我的问题是:在第二种情况下,PostgreSQL会逐行锁定行,还是锁定WHERE条件所涵盖的整个范围?

提前致谢!

kgr*_*ttn 39

在PostgreSQL中,行会在更新时被锁定 - 实际上,这实际上工作的方式是每个元组(行的版本)都有一个系统字段,xmin用于指示哪个事务使该元组成为当前(通过插入或更新)和一个系统字段,调用xmax以指示哪个事务过期了该元组(通过更新或删除).当您访问数据时,它会检查每个元组,以确定您的事务是否可见,方法是根据这些值检查您的活动"快照".

如果您正在执行UPDATE并且与您的搜索条件匹配的元组具有xmin,这将使您的快照和活动事务的xmax可见,它将阻塞,等待该事务完成.如果首次更新元组的事务回滚,则事务会唤醒并处理该行; 如果第一个事务提交,则事务会唤醒并根据当前事务隔离级别执行操作.

显然,死锁是以不同顺序发生在行中的结果.RAM中没有行级锁定,可以同时为所有行获取,但如果行以相同的顺序更新,则无法进行循环锁定.不幸的是,建议的IN(1, 2)语法并不能保证.不同的会话可能有不同的成本因素活动,后台"分析"任务可能会更改一个计划的生成与另一个计划之间的表的统计信息,或者它可能正在使用seqscan并受PostgreSQL优化的影响,导致新的seqscan加入一个已在进行中并"循环"以减少磁盘I/O.

如果您以相同的顺序,应用程序代码或使用游标一次执行一个更新,那么您将只有简单的阻塞,而不是死锁.但是,一般而言,关系数据库容易出现序列化失败,最好通过一个框架访问它们,该框架将基于SQLSTATE识别它们并从一开始就自动重试整个事务.在PostgreSQL中,序列化失败的SQLSTATE总是为40001或40P01.

http://www.postgresql.org/docs/current/interactive/mvcc-intro.html

  • 锁定一直持续到提交或回滚. (7认同)
  • 它可能会导致僵局,尽管这种情况很少见;与第一个示例(明确选择不同的订单)相反,它很常见。您可以通过在每次更新表的事务期间采用适当强度的表级锁来排除死锁,但这种治疗可能比疾病更糟糕。有关详细信息,请参阅我引用的文档部分。 (2认同)

Pau*_*per 6

PostgreSQL 是逐一锁定行,还是锁定整个范围

PostgreSQL 逐一锁定行。

令人沮丧的是,没有办法像选择和插入那样对更新(或删除)进行排序。

SELECT FOR UPDATE解决方案是预先使用自连接锁定记录。

UPDATE customer AS c SET ...
FROM (
  SELECT ctid
  FROM customer
  WHERE id IN (1, 2)
  ORDER BY id -- the optimal ordering varies, but it must be strict and consistent
  FOR UPDATE
) AS c2
WHERE c.ctid = c2.ctid
Run Code Online (Sandbox Code Playgroud)

(这里我使用行的物理 ID ctid,这对于连接来说可能会稍微快一些。)

PostgreSQL会找到记录,按顺序锁定记录,然后更新记录。

您可以检查查询计划来说服自己这一点。

有一些开销,但它是最小的,特别是考虑到这UPDATE通常不是一个轻量级的操作。