假设表
a
和b
都只有一行数据,在查询时: SELECT * FROM a, b FOR UPDATE
应该获得两个行级锁(一个在a上,一个在b上)。这些锁的获取是否有定义的顺序?是否有任何方法要求从表b获取的锁在从a获取的锁之前获取(以避免与其他事务发生死锁)?
a
和b
都只有一行数据,在查询时: SELECT * FROM a, b FOR UPDATE
应该获得两个行级锁(一个在a上,一个在b上)。这些锁的获取是否有定义的顺序?是否有任何方法要求从表b获取的锁在从a获取的锁之前获取(以避免与其他事务发生死锁)?
在获取锁的过程中,有没有定义好的顺序?
就我所知,在使用 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
是做什么的呢?这时候就需要去看源代码了。src/backend/executor/nodeLockRows.c
中的ExecLockRows
是相关的代码。其中有很多内容,但其要点是按顺序遍历RowMark
列表,并依次获取每个锁。该列表由ExecInitLockRows
设置,它复制并过滤了在计划期间准备好并存储在LockRows
节点中的列表。
我没有时间追溯计划器以找到LockRows
创建的顺序,但如果我没记错的话,基本上只是解析顺序(对于SELECT *
)或字段出现在SELECT
列表中的顺序(如果你没有使用*
)。我建议不要依赖它。