SELECT … ORDER BY xxx LIMIT 1 FOR UPDATE 将锁定多少行?

gke*_*rus 6 postgresql deadlock locking

正如在stackoverflow 上被问到的那样,但是对于 MySQL,我想知道它在 PostgreSQL 中是如何工作的。当我将“FOR UPDATE”与“LIMIT”、“ORDER BY”和一些“WHERE”组合在一起时,锁定了多少行。他们更新返回的行。我希望“FOR UPDATE”查询只会锁定一行,但也许我错过了一些实现问题。(我打算写一些压力测试来更可靠地重现这一点)

这些查询导致我的系统出现死锁:

(session1) select field1, field2, ... from documents where transaction_path='some/path' for update
(session2) select field1, field2, ... from documents where (transaction_path is null) and queue_id=2 and next_pickup_ts<now() order by next_pickup_ts limit 1 for update
(session3) select field1, field2, ... from documents where (transaction_path is null) and queue_id=2 and next_pickup_ts<now() order by next_pickup_ts limit 1 for update
Run Code Online (Sandbox Code Playgroud)

但我不明白为什么。(transaction_path 上有一个唯一索引。)

Cra*_*ger 7

如果您使用,EXPLAIN您将看到排序。对于我用一些一次性数据和类似查询 ( ORDER BY ... LIMIT 1 FOR UPDATE)炮制的随机计划,我得到了计划:

 Limit  (cost=33.09..33.10 rows=1 width=26)
   ->  LockRows  (cost=33.09..39.88 rows=543 width=26)
         ->  Sort  (cost=33.09..34.45 rows=543 width=26)
               Sort Key: t_id
               ->  Seq Scan on m  (cost=0.00..30.38 rows=543 width=26)
                     Filter: (b_id <= 33)
(6 rows)
Run Code Online (Sandbox Code Playgroud)

在这里,您可以看到在应用限制之前行可能被锁定。

现在,在实践中,匹配的第一行通常会被锁定并返回,导致LockRows后面的行被跳过……但不能保证这一点。所以你不应该依赖它。

一般来说,混合行锁定和LIMIT是一个坏主意,通常应该避免。

如果你必须这样做,你会想要做这样的事情:

SELECT *
FROM my_table
WHERE id = (SELECT id FROM my_table 
            WHERE mywhereclause 
            ORDER BY ... LIMIT 1)
  AND mywhereclause
FOR UPDATE;
Run Code Online (Sandbox Code Playgroud)

您强制找到目标行,然后才锁定。

您应该在外部查询中重复 WHERE 子句,以确保在发生任何锁定等待后该行仍然与谓词匹配。否则可能发生的情况是内部查询找到该行,返回 ID,然后您尝试使用该 ID 锁定该行。PostgreSQL 看到其他人对该行有锁,因此它等待该锁被释放。然后它重新检查WHERE外部查询上的子句以确保它仍然匹配(以防该行被删除或被删除UPDATE)。因为内部查询是一个不相关的子查询,所以它不会重新计算它,所以内部WHERE子句不会重新运行,WHERE如果你不重复,你可以获得与返回的内部子句不再匹配的行它在外部WHERE条款中。

混合行限制和行锁定很困难。构建实际上比单个工人表现更好的正确排队系统甚至更难。使用现成的排队系统并为自己省去很多麻烦。

PostgreSQL 9.5 将有SKIP LOCKED,这使得这种方式更容易,但从现在起一年后不会有稳定的版本,所以不要屏住呼吸。)