【问题标题】:Rails ActiveRecord - how can I lock a table for reading?Rails ActiveRecord - 如何锁定表以供阅读?
【发布时间】:2014-07-05 14:27:52
【问题描述】:

我有一些 Rails ActiveRecord 代码,如下所示:

new_account_number = Model.maximum(:account_number)
# Some processing that usually involves incrementing
# the new account number by one.
Model.create(foo: 12, bar: 34, account_number: new_account_number)

此代码本身运行良好,但我有一些由 DelayedJob 工作人员处理的后台作业。有两个工人,如果他们都开始处理一批处理此代码的作业,他们最终会创建具有相同 account_number 的新 Model 记录,因为在找到最大值和创建新记录之间存在延迟更高的帐号。

目前,我已通过在模型表中添加数据库级别的唯一性约束来解决此问题,然后通过重新选择最大值来重试,以防此约束触发异常。

但是感觉就像是黑客攻击。

将数据库级别的自动递增添加到account_number 列不是一种选择,因为分配 account_number 不仅仅需要递增。

理想情况下,我想锁定有问题的表以供阅读,因此在我完成之前,没有其他人可以对该表执行最大选择查询。但是,我不知道该怎么做。我正在使用 Postgresql。

【问题讨论】:

  • 约束不是黑客,手动锁定表是黑客。
  • EXCLUSIVE 锁定表,这样可以读取数据

标签: sql ruby-on-rails postgresql activerecord locking


【解决方案1】:

基于the ActiveRecord::Locking docs,Rails 似乎没有为表级锁提供内置 API。

但您仍然可以使用原始 SQL 执行此操作。对于 Postgres,这看起来像

ActiveRecord::Base.transaction do
  ActiveRecord::Base.connection.execute('LOCK table_name IN ACCESS EXCLUSIVE MODE')
  ...
end

锁必须在事务中获取,并且在事务结束后自动释放。

请注意,您在此处使用的 SQL 会因您的数据库而异。


显然锁定整个表格并不优雅也不高效,但对于小型应用程序来说,在一段时间内,它可能确实是最好的解决方案。这很简单,很容易推理。一般来说,咨询锁更适合这种数据竞争。

【讨论】:

    【解决方案2】:

    已经有关于如何锁定整个表的答案,但我相信你应该尽量避免这种情况。相反,我相信你应该看看咨询锁。它确保相同的代码块不会同时在两台机器上执行,同时仍然保持表对其他业务开放。

    它仍然使用数据库,但不会锁定您的表。

    您可以像这样使用名为“with_advisory_lock”的 gem:

    Model.with_advisory_lock("ADVISORY_LOCK_NAME") do
      # Your code
    end
    

    https://github.com/ClosureTree/with_advisory_lock

    它不适用于 SQLite。

    【讨论】:

    • 非常好,但是增加了gem依赖。 :)
    • 在同一台机器上运行时是否有效。 IE,sidekiq 与多个工人
    • @AntarrByrd 是的 :-)
    【解决方案3】:

    设置唯一约束不是 hack。它使您的数据保持一致。 顺便说一句,您还有更多选择:

    1. 锁定一些数据库资源(例如,它可能是唯一的记录)使用 SELECT FOR UPDATE 或 PostreSQL 的咨询锁(请参阅docs)。

    2. 使用序列 (docs)。

    两种方法之间的主要区别是#1 不允许你的数字有差距,因为其他会话将等待事务提交,而 #2 允许。

    【讨论】:

      【解决方案4】:

      您不必锁定大厅桌子来一次锁定单个进程的一段代码。锁定一个完整的表会导致性能问题。您可以使用“with_lock”方法一直锁定同一行。这样代码就得到了完全的保护。不需要额外的宝石。它还创建了一个事务。像这样: m = Model.order(:id).first m.with_lock do #aquire lock #some code here for a single process at a time
      end #release lock

      【讨论】:

        【解决方案5】:

        嗯,从技术上讲,锁定一个表或在访问该表之前始终锁定另一个表的记录是相同的。

        所以你可能有另一个表最多有一条记录,在从你想锁定的表中读/写之前,总是用http://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html 锁定该记录:

        LockTable.last.with_lock do
          // the things that needed for your table
        end
        

        【讨论】:

        • 人们应该在投票前发表评论
        猜你喜欢
        • 1970-01-01
        • 2012-04-22
        • 1970-01-01
        • 2023-03-19
        • 1970-01-01
        • 2021-07-17
        • 1970-01-01
        • 2013-11-17
        • 2020-06-17
        相关资源
        最近更新 更多