我们的 postgres 数据库报告了关系中元组的大量死锁。 只有两个函数使用该关系,通常只有一个函数涉及死锁。
最常导致死锁的函数有两个查询:
1. The first query
looks for ONE photo
and ROW LOCKS ALL the photo rows
for ALL albums the the photo is found in
For example given the below table of data:
if the query was looking for Photo 2
then it would LOCK ALL 6 rows of Album A and C.
album photo version
A 1 1.0 lock
A 2 1.0 lock update
A 3 1.0 lock
B 8 2.0
B 9 2.0
C 1 1.1 lock
C 2 1.1 lock update
C 5 1.1 lock
D 7 4.0
D 8 4.0
2. The second query then updates the 2 tuples for Photo 2.
FOR UPDATE 和 UPDATE 查询使用以下查询以相同的顺序访问元组。
据我了解,如果元组始终按相册和照片顺序访问,则不会出现死锁。
该函数每秒被调用多次,我确实预料到会发生阻塞,但无法解释死锁。
感谢任何帮助。
函数“album_version_set”中的查询
PERFORM 1
FROM work.album a
WHERE EXISTS (
SELECT
x.album
FROM work.album x
WHERE
x.photo = 2
AND x.album = a.album)
ORDER BY
a.album,
a.photo
FOR UPDATE;
WITH cte_update_version (album) AS (
UPDATE work.album a
SET
version = version + .1
FROM (
SELECT
x.album,
x.photo
FROM work.album x
WHERE
x.photo = 2
ORDER BY
x.album
x.photo
) ord
WHERE
a.album = ord.album
AND a.photo = ord.photo
RETURNING
a.album)
INSERT INTO tmp_album_keys(
album)
SELECT DISTINCT
us.album
FROM
cte_update_version;
为这个问题添加更多内容:
从错误日志中我可以看出函数“album_version_set”与自身冲突并导致死锁。
下面是日志中的条目。日志似乎只显示了死锁中涉及的进程之一的语句。由于此函数有两个查询,我不确定进程 31019 中的哪个查询是死锁的一部分。
这是日志中的一个条目:
2018-03-06 15:35:20 UTC:10.1.2.1(43636):z1login@atier:[31024]:ERROR: deadlock detected
2018-03-06 15:35:20 UTC:10.1.2.1(43636):z1login@atier:[31024]:DETAIL: Process 31024 waits for ShareLock on transaction 8334317; blocked by process 31019.
Process 31019 waits for ShareLock on transaction 8334322; blocked by process 31024.
Process 31024: SELECT * FROM album_version_set($1, $2)
Process 31019: SELECT * FROM album_version_set($1, $2)
2018-03-06 15:35:20 UTC:10.1.2.1(43636):z1login@atier:[31024]:HINT: See server log for query details.
2018-03-06 15:35:20 UTC:10.1.2.1(43636):z1login@atier:[31024]:CONTEXT: while locking tuple (11,83) in relation "album"
SQL statement "SELECT 1
FROM work.album a
WHERE EXISTS (
SELECT
x.album
FROM work.album x
WHERE
x.photo = 2
AND x.album = a.album)
ORDER BY
a.album,
a.photo
FOR UPDATE;"
PL/pgSQL function album_version_set(character varying,smallint) line 69 at PERFORM
2018-03-06 15:35:20 UTC:10.1.2.1(43636):z1login@atier:[31024]:STATEMENT: SELECT * FROM album_version_set($1, $2)
最佳答案
看起来至少有一个竞争条件可能会导致死锁(默认情况下是 transaction isolation level ,无论如何),但我不能确定它会导致你的问题。
假设您的表格最初看起来像这样:
album photo version
B 2 1.0
C 2 1.0
您的第一个查询运行,并开始锁定行。
与此同时,其他人运行 INSERT INTO work.album VALUES ('A', 2, 1.0)
。
这个新行被 FOR UPDATE
查询忽略(因为它的数据库快照固定在语句的开头),但它仍然被后续的 UPDATE
,并在进程中被锁定。
总的来说,您交易中的锁定顺序(根据 album
值)是 'B'
、'C'
、'A'
;你现在有陷入僵局的风险。
更糟糕的是,如果并发插入包含多行,那么您已经用 photo = 2
更新了记录,而没有锁定相册的其余部分。例如,如果并发语句是 INSERT INTO work.album VALUES ('A', 2, 1.0), ('A', 3, 1.0)
,那么您将留在以下内容中状态:
album photo version
A 2 1.0 update
A 3 1.0
B 2 1.0 lock update
C 2 1.0 lock update
一般来说,在 FOR UPDATE
查询和 UPDATE
语句中重复相同的 WHERE
条件会使您容易受到这些类型的死锁的影响.避免此问题的一般模式是让您的锁定查询返回一些明确的行标识符(如果有生成的主键,如果没有,则为 ctid
*)以明确锁定的内容,然后将这些标识符传递给 UPDATE
语句以确保它只针对锁定的元组,例如:
DECLARE
locked_tuples tid[];
BEGIN
locked_tuples := ARRAY(
SELECT ctid
FROM work.album
WHERE album IN (
SELECT x.album
FROM work.album x
WHERE x.photo = 2
)
ORDER BY album, photo
FOR UPDATE
);
WITH cte_update_version (album) AS (
UPDATE work.album
SET version = version + .1
WHERE
ctid = ANY(locked_tuples) AND
photo = 2
RETURNING album
)
INSERT INTO tmp_album_keys(album)
SELECT DISTINCT album
FROM cte_update_status;
END
这应该消除死锁的可能性,尽管这也意味着并发插入的行将不再更新(这可能是也可能不是您所希望的)。
* 注意 ctid
值。它们不能被视为通用行标识符,因为它们可以通过各种内部操作进行更改,但只要您持有该行的锁,它们就应该是稳定的。
关于postgresql - 这个 Postgres 函数怎么会死锁?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49121102/