【问题标题】:Put pg_try_advisory_xact_lock() in a nested subquery?将 pg_try_advisory_xact_lock() 放在嵌套子查询中?
【发布时间】:2015-10-14 14:45:01
【问题描述】:

在我的 Ruby on Rails 4 应用程序中,我对 Postgres 9.4 数据库进行了以下查询:

@chosen_opportunity = Opportunity.find_by_sql(
  " UPDATE \"opportunities\" s
    SET opportunity_available = false
    FROM (
          SELECT \"opportunities\".*
          FROM   \"opportunities\"
          WHERE  ( deal_id = #{@deal.id}
          AND    opportunity_available = true 
          AND    pg_try_advisory_xact_lock(id) )
          LIMIT  1
          FOR    UPDATE
          ) sub
    WHERE       s.id = sub.id
    RETURNING   sub.prize_id, sub.id"
)

非常受this related answer on dba.SE的启发。

但是在这里 (Postgres pg_try_advisory_lock blocks all records) 他们说,如果我没记错的话,我不应该在 WHERE 子句中使用 pg_try_advisory_lock(),因为我会称它为 在被扫描的整个集合中每行一次(作为 where 子句中发生的过滤的一部分)。

我只想让我的查询找到并更新第一行(随机,LIMITavailable = true 并将其更新为available = false,我需要在执行此操作时锁定该行,但不创建新行请求等待释放前一个锁,所以我添加了咨询锁like suggested here

我应该将pg_try_advisory_lock() 放在WHERE 子句之外吗?怎么做?

【问题讨论】:

  • @patrick,没有其他问题 (stackoverflow.com/questions/33129132/…) 是关于相同的查询,但主题不同:我应该使用 pg_try_advisory_lock 还是 NOWAIT 选项?同一代码上的完全不同的问题。
  • @patrick,或者纯属巧合,因为我是 postgresql 菜鸟,NOWAIT 是当前问题的答案?

标签: sql ruby-on-rails ruby-on-rails-3 postgresql concurrency


【解决方案1】:

我用更多解释和链接更新了我的参考答案。
在 Postgres 9.5(目前为测试版)中,新的 SKIP LOCKED 是一个出色的解决方案:


让我先在您的查询中简化一些事情:

直接查询

UPDATE opportunities s
SET    opportunity_available = false
FROM  (
   SELECT id
   FROM   opportunities
   WHERE  deal_id = #{@deal.id}
   AND    opportunity_available
   AND    pg_try_advisory_xact_lock(id)
   LIMIT  1
   FOR    UPDATE
   ) sub
WHERE     s.id = sub.id
RETURNING s.prize_id, s.id;
  • 所有双引号都只是您合法的小写名称的噪音。
  • 由于opportunity_available 是一个布尔列,您可以将opportunity_available = true 简化为opportunity_available
  • 您不需要从子查询中返回*,只需id 就足够了。

通常,这按原样工作。解释如下。

避免对不相关行的咨询锁定

可以肯定的是,您可以使用OFFSET 0 hack 将所有谓词封装在 CTE 或子查询中(开销更少)在下一个查询级别应用 pg_try_advisory_xact_lock() 之前:

UPDATE opportunities s
SET    opportunity_available = false
FROM (
   SELECT id
   FROM  ( 
      SELECT id
      FROM   opportunities
      WHERE  deal_id = #{@deal.id}
      AND    opportunity_available
      AND    pg_try_advisory_xact_lock(id)
      OFFSET 0
      ) sub1
   WHERE  pg_try_advisory_xact_lock(id)
   LIMIT  1
   FOR    UPDATE
   ) sub2
WHERE     s.id = sub.id
RETURNING s.prize_id, s.id;

但是,这通常要贵​​得多。

你可能不需要这个

如果您的查询基于涵盖所有谓词的索引,则不会有任何“附带”咨询锁,例如这个部分索引:

CREATE INDEX opportunities_deal_id ON opportunities (deal_id)
WHERE opportunity_available;

检查EXPLAIN 以验证 Postgres 实际使用索引。这样,pg_try_advisory_xact_lock(id) 将成为索引或位图索引扫描的过滤条件,并且只有符合条件的行才会被测试(并锁定),因此您可以使用简单的形式而无需额外的嵌套。同时,您的查询性能也得到了优化。我会这样做

即使几个不相关的行偶尔会获得咨询锁,这通常也没关系。咨询锁只与实际使用咨询锁的查询相关。或者您是否真的有其他并发事务也使用咨询锁并针对同一表的其他行?真的吗?

唯一有问题的情况是,如果大量不相关的行获得咨询锁,这只能在顺序扫描中发生,而且即使那样也不太可能发生。

【讨论】:

  • 我不知所措,为查询而苦苦挣扎,所以我开了一个新问题:stackoverflow.com/questions/33330915/…
  • 转移到另一个问题:我认为问题来自于快速,(你在这里的直觉中发现它非常胖,呵呵)我需要从任意选择转移到真正的随机选择,同时拥有最快的选择/更新查询和锁定:stackoverflow.com/questions/33333021/…
  • @Erwin 在具有密集插入/更新/删除(如队列)的表中,这种方法不可靠。我想主要问题是为计划者收集的过时统计数据。我有大约 300 个项目/秒的“队列”表。我将表的自动清理调整为:autovacuum_vacuum_threshold=5000,autovacuum_vacuum_scale_factor=0.0,autovacuum_analyze_threshold=5000,autovacuum_analyze_scale_factor=0.0,在某些情况下,当队列包含大约 100 000 个项目时,我会收到“共享内存不足”错误。针对表修复问题手动运行“vacuum full”一段时间。
  • @Oleksandr_DJ:我想会话级锁定和手动解锁可以解决这个特殊问题。请参阅instructions in the superior answer I linked to. 但首先考虑新的SKIP LOCKED
  • @Oleksandr_DJ:对于只有 一个 阅读器(无并发),您根本不需要手动锁定。如果您仍需要锁定,请考虑设置 max_locks_per_transaction 的手册。考虑PGQ 以获得更有效的排队解决方案。如果仍有疑问,我建议您提出一个新问题,提供所有相关信息。评论不是地方。最好在 dba.SE 上。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-16
  • 1970-01-01
  • 2017-11-08
  • 2019-04-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多