【问题标题】:how to get value from database when concurrent saving multiple data并发保存多个数据时如何从数据库中获取价值
【发布时间】:2021-09-10 05:19:56
【问题描述】:
class Defect < ApplicationRecord
  has_many :work_orders, dependent: :destroy
end
class WorkOrder < ApplicationRecord
  belongs_to :defect
  before_save :default_values

  def default_values
    self.running_number = self.defect.work_orders.maximum(:running_number).to_i + 1 if self.new_record?
  end
end

理想情况下,代码是这样工作的

Defect A 
- Work Order running_number 1
- Work Order running_number 2
- Work Order running_number 3

Defect B
- Work Order running_number 1
- Work Order running_number 2
- Work Order running_number 3

但是,当多个用户同时保存属于同一缺陷的不同 WorkOrder 对象时,running_number 将变得混乱,因为 maximum_running_number 仅基于保存的数据。 如何让 running_number 正确保存?

【问题讨论】:

    标签: ruby ruby-on-rails-5


    【解决方案1】:

    问题是您的并发保存会获得相同的工单计数,因此您会为工单获得重复的 running_numbers。

    你可以通过两种方式解决它:

    • running_numberdefect_id 上设置唯一约束
    • 在您提交新的工作订单之前,获取对工作订单表的锁定。

    在 Rails 迁移中设置唯一约束:add_index :work_orders, [:defect_id, :running_number], unique: true。如果调用save时出现错误,则只需重试保存即可。

    假设您使用的是 Postgres

      begin 
        # .. create the work order
        work_order.save
      rescue PG::UniqueViolation
        retry
      end
    

    使用retry 将重试阻止,直到没有引发唯一违规。如果记录中存在其他一些独特的违规错误,这可能会导致死锁,因此请确保该错误是由 running_number 引起的,而不是其他原因。

    另一种方法是获取锁以防止竞争条件。由于它是共享资源的数据库表,因此在计算工单数量并保存记录时,您需要获取表锁以确保没有其他进程正在使用工单表。

    假设您使用 Postgres explicit-locking docs

    ActiveRecord::Base.transaction do
      # create order
    
      ActiveRecord::Base.connection.execute('LOCK work_orders IN ACCESS EXCLUSIVE MODE')
      
      work_order.save
    end
    

    使用此模式获取表锁将阻止其他数据库连接对表的所有访问。它会在事务提交时被释放,但如果由于某种原因 ruby​​ 进程在有机会完成事务块之前被杀死,则可能再次导致死锁。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-11-15
      • 2018-08-24
      • 1970-01-01
      • 2016-09-12
      • 2015-05-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多