回答

收藏

将pg_try_advisory_xact_lock()放在嵌套的子查询中?

技术问答 技术问答 45 人阅读 | 0 人回复 | 2023-09-13

在我的Ruby on Rails 4应用程序中,我有以下查询到Postgres 9.4数据库:# z( w# Y! J% C% M9 u9 X& T
@chosen_opportunity = Opportunity.find_by_sql(; O' Q! _  I: s- d
  " UPDATE \"opportunities\" s" J' W( I7 f. l  S
    SET opportunity_available = false
7 a" k8 X% L: ?* b4 y  m  ~$ a    FROM (
2 n" }1 g2 x, B( q2 b# H: y& a          SELECT \"opportunities\".*- N" M( y9 L3 L! g# ?2 |7 w
          FROM   \"opportunities\") F8 P/ W/ n/ b9 x
          WHERE  ( deal_id = #{@deal.id}
* V% J' U- \" K: f          AND    opportunity_available = true
# E& m' X! m0 `          AND    pg_try_advisory_xact_lock(id) ); T# V& g- b5 ]* \
          LIMIT  10 ^. t1 q5 n* ]( u6 z. t& U2 i/ S
          FOR    UPDATE- d. |5 J) a8 B+ h1 ^% u( U
          ) sub. Z4 E, O" S3 f5 C) P
    WHERE       s.id = sub.id9 p0 ^' E- N* J$ P
    RETURNING   sub.prize_id, sub.id"( e: V1 l3 b5 s& I
)1 ^: [7 Q* ?/ v+ `; k# j
但是在这里Postgrespg_try_advisory_lock阻止了所有记录他们说,如果我没记错的话,我不
: G, [( s6 `) k: V- e; Q应该pg_try_advisory_lock()在WHERE子句中使用,因为我将在被扫描的整个集合中的 每一行 调用 一次. E( }7 R. L" ~$ w9 C) @4 B9 Z
(作为过滤的一部分)出现在where子句中)。2 T1 V3 x, T. \2 R4 {$ }" u2 @" Y
我只希望查询在其中找到并更新第一行(随机地LIMIT),available = true并将其更新为available =
& |4 U4 P) J1 X! rfalse,并且我需要在执行此操作时锁定该行,但无需发出新请求来等待先前锁的释放,因此我添加了像这里建议的咨询锁。, C! V6 c& i2 Y# b
我应该放在条款pg_try_advisory_lock()之外WHERE吗?怎么做?
* Q6 A/ [  I: C& R2 \; a% p/ h5 j                4 B) {8 I' {- M, ]( B8 x1 `4 Z
解决方案:: G7 X5 v8 j$ R2 I1 K
               
7 z& v! }4 @: T# E6 I8 H. ]( d- D! M8 [* g6 \

; O" F+ l7 D5 f- U" M0 Q7 p2 d; ]                我更新了参考答案,并提供了更多解释和链接。3 b+ R3 x$ x: E/ x; k
在Postgres 9.5(目前为beta)中,新功能SKIP LOCKED是一种出色的解决方案:  I$ q: ~4 y, j
Postgres UPDATE-LIMIT 1" }2 J. h; S2 g" p9 f+ c

; s* G2 g. y0 B. ]3 h首先让我简化一下查询中的几件事:
9 j2 T7 h9 `, ^$ u直接查询' o! l* m8 D7 g. V- K' N& K' ]! N
UPDATE opportunities s; y2 M0 }4 a% ]% o6 w: ~# ^8 x
SET    opportunity_available = false% V" C% s9 v0 `, l6 r) N. S6 q0 \' }
FROM  (
8 F0 r! W3 a* u. X0 ~( ]- |- Y   SELECT id7 d5 w5 R5 Z' ~2 D. ]8 Z( W9 z
   FROM   opportunities/ z6 {3 }1 |9 J/ o# f- B( V  E3 P" y
   WHERE  deal_id = #{@deal.id}
: [. F) P; @0 E* K" L9 f   AND    opportunity_available
$ m7 p7 O, H" y0 F5 g+ I) R& G   AND    pg_try_advisory_xact_lock(id)9 R1 Q3 w* }, O, s
   LIMIT  1
; S- h* Y; t# o   FOR    UPDATE
8 A5 X- j3 P9 N2 r7 N   ) sub0 I4 h. x; `2 ?& @4 \0 Q
WHERE     s.id = sub.id
/ T  o% c0 z$ v& t0 h0 \- GRETURNING s.prize_id, s.id;
# c1 [% O: O6 O# N# R所有的双引号都带有合法的小写字母名称。5 f, A+ `! w2 u( [! r9 r, o
由于career_available是一个布尔列,因此您可以简化opportunity_available = true为opportunity_available
  K; {; e+ f5 @您不需要*从子查询返回,就id足够了。
! S% M8 G2 {1 r3 K- ?- f

) L$ }' I) ]( P6 x4 a% ^6 V通常,它 按原样 工作。解释如下。
/ Y( q4 \0 w# `4 E( Z: b/ i8 {避免对不相关的行进行咨询性锁定
5 E. n5 F6 T  w可以肯定的是, 在 应用到下一个查询级别 之前 ,您可以将所有谓词封装在带有OFFSET 0黑客的CTE或子查询中(减少开销):
; i1 K( g6 h, L" w6 c" @__pg_try_advisory_xact_lock()
% ]5 I9 K( n/ B8 H: @4 ~& Z7 d/ HUPDATE opportunities s
# ^& G% i. A+ a. O% C5 MSET    opportunity_available = false
8 k' N2 X9 j% \: G1 ?% mFROM (
: R. o  a6 H' i* T' b" a4 `   SELECT id
8 F- h1 z5 ?( p& P$ `, i1 n   FROM  (
+ T7 ], }# e1 b# t      SELECT id8 [& B5 r, R: L9 `9 m
      FROM   opportunities4 h! e. X9 ?( x7 z( L
      WHERE  deal_id = #{@deal.id}
' v' v+ h$ a& o9 |, Z/ |. [  v      AND    opportunity_available0 R0 A3 {7 m! V$ i
      AND    pg_try_advisory_xact_lock(id). h# O# E4 K( S
      OFFSET 0
. b3 g1 c$ y5 ?& {2 G7 g+ z      ) sub13 h3 e# _$ J% V: M2 b. Q' W
   WHERE  pg_try_advisory_xact_lock(id)
. A* P7 @- \' Q   LIMIT  12 f2 a. W0 C1 Y4 M8 Q# q
   FOR    UPDATE
8 }# r+ f( C' D' @8 G   ) sub27 ?" }5 d  D7 R4 r/ k/ u
WHERE     s.id = sub.id" E# Z, f7 O0 E- G+ ^3 a: k0 C' j
RETURNING s.prize_id, s.id;
1 W8 o; o$ |( ^1 b7 I& z' ~) c但是 ,这通常要贵??得多。
! k' S1 a; i; Q) J你可能不需要这个. G3 D9 W! l* ^- X1 A) \" V; V
如果您将查询基于覆盖所有谓词的索引(例如,部分索引),则不会有任何“附带”咨询锁:
; j0 {4 P2 k9 L$ X' D3 o7 SCREATE INDEX opportunities_deal_id ON opportunities (deal_id): ], H$ w' [5 a$ T% N4 P! l
WHERE opportunity_available;
5 b* H8 Q, \; N7 p$ U检查EXPLAIN以验证Postgres实际使用该索引。这样,pg_try_advisory_xact_lock(id)将成为索引或位图索引扫描的筛选条件,并且仅将对合格行进行测试(并锁定),因此您可以使用简单的表单而无需其他嵌套。同时,您的查询性能得到了优化。我会做
6 |$ w7 d9 t+ b9 G. T+ R$ C+ Y那 。1 p3 f0 z& z' m" |0 p# L+ Q
即使 几个不相关的行 _ 偶尔_& m, Z0 F" H6 R& {
会获得咨询锁,通常也没关系。咨询锁仅与实际使用咨询锁的查询有关。还是您真的有其他并发事务也使用咨询锁并定位同一表的其他行?真的吗?' P! i. t* O, ^
唯一有问题的情况是,如果大量不相关的行获得了咨询锁,那么只有在顺序扫描时才会发生这种情况,即使在那时也不太可能。
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则