postgres中的锁定顺序SELECT FOR UPDATE

时间:2015-03-13 20:24:57

标签: postgresql

假设表ab都有一行,那么查询

 SELECT * FROM a, b FOR UPDATE

应该获得两个行级锁(一个在a上,一个在b上)。是否有任何已定义的锁定获取顺序?有没有办法要求表b中的锁从a之前得到锁定(以避免与其他事务发生死锁)?

1 个答案:

答案 0 :(得分:5)

  

是否有获取锁定的已定义顺序?

我不知道,对SELECT *而言。由于没有记录此情况下的锁定顺序,即使在实践中存在,也不能依赖它。它可能会在未来版本中发生变化。

  

有没有办法要求表b中的锁从a之前得到锁定(以避免与其他事务发生死锁)?

如果你必须使用SELECT *,那么不。但是,如果你可以控制SELECT - 列表,是的。看起来行锁是按照SELECT列表中相关元组字段的顺序获取的,所以:

SELECT a.x, b.x FROM b, a FOR UPDATE;

将从a获取对b行上的行的锁定。目前,无论如何;我不认为标准指定了这一点,也没有在文档中看到对它的任何引用,因此以后可能会更改

个人......我使用DO块或单独的查询。有可能使用一些子查询或CTE来执行此操作,但您必须在它们之间创建某种形式的人工依赖关系以确保排序。脆弱而不值得。


让我们看一下实际发生的事情:

regress=> EXPLAIN (VERBOSE) SELECT * FROM a, b FOR UPDATE;
                                  QUERY PLAN                                   
-------------------------------------------------------------------------------
 LockRows  (cost=0.00..129674.00 rows=5760000 width=20)
   Output: a.x, b.x, a.ctid, b.ctid
   ->  Nested Loop  (cost=0.00..72074.00 rows=5760000 width=20)
         Output: a.x, b.x, a.ctid, b.ctid
         ->  Seq Scan on public.a  (cost=0.00..34.00 rows=2400 width=10)
               Output: a.x, a.ctid
         ->  Materialize  (cost=0.00..46.00 rows=2400 width=10)
               Output: b.x, b.ctid
               ->  Seq Scan on public.b  (cost=0.00..34.00 rows=2400 width=10)
                     Output: b.x, b.ctid
(10 rows)

执行查询,然后将结果输入LockRows节点。 LockRows做了什么?为此,现在是时候进行源代码潜水了。

ExecLockRows in src/backend/executor/nodeLockRows.c是相关代码。那里有很多,但它的要点是它按顺序迭代RowMark列表并按顺序获取每个锁。该列表由ExecInitLockRows设置,其中它复制并过滤在规划期间准备并存储在LockRows节点中的列表。

我没有时间追溯到规划器中查找LockRows创建的顺序,但是IIRC它基本上只是解析顺序(SELECT *)或字段在SELECT列表中的显示顺序(如果您没有使用*)。我建议不要依赖它。