38

我对 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

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

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

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

这里会有死锁的可能吗?

本质上我的问题是:在第二种情况下,PostgreSQL 是一个接一个地锁定行,还是锁定条件覆盖的整个范围WHERE

提前致谢!

4

2 回答 2

46

在 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

于 2012-04-20T12:14:34.593 回答
1

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

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

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

您可以检查查询计划以使自己相信这一点。

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

于 2022-02-17T18:37:30.623 回答