|
在我的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, ^
唯一有问题的情况是,如果大量不相关的行获得了咨询锁,那么只有在顺序扫描时才会发生这种情况,即使在那时也不太可能。 |
|