我们的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'与自身冲突并导致死锁。
以下是日志中的条目。似乎日志只显示死锁中涉及的1个进程的语句。由于此函数有两个查询,我不确定进程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)
答案 0 :(得分:1)
看起来至少有一个竞争条件可能存在死锁(无论如何都是默认的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
通常,在WHERE
查询和FOR UPDATE
语句中重复相同的UPDATE
条件会使您容易受到这些类型的死锁的影响。避免此问题的一般模式是让您的锁定查询返回一些明确的行标识符(如果您有一个生成的主键,或者失败,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
值。它们不能被视为通用行标识符,因为它们可以通过各种内部操作进行更改,但只要您对行进行锁定它们就应该是稳定的。