并发保存多个数据时如何从数据库中获取值
how to get value from database when concurrent saving multiple data
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?
问题是您的并发保存获得了相同数量的工单,因此您获得了重复的工单 running_numbers。
您可以通过两种方式解决:
- 在
running_number
和 defect_id
上设置唯一约束
- 获取工作订单的锁 table,直到您提交新的工作订单。
要在 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
而不是其他原因引起的。
另一种方法是获取锁以防止竞争条件。由于它的数据库 table 是共享资源,您需要一个 table 锁以确保在计算工作订单数量和保存时没有其他进程正在使用工作订单 table记录。
假设您使用的是 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
使用此模式获取 table 锁将阻止其他数据库连接对 table 的所有访问。它会在事务提交时释放,但如果 ruby 进程在它有机会完成事务块之前被终止,则可能再次导致死锁。
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?
问题是您的并发保存获得了相同数量的工单,因此您获得了重复的工单 running_numbers。
您可以通过两种方式解决:
- 在
running_number
和defect_id
上设置唯一约束
- 获取工作订单的锁 table,直到您提交新的工作订单。
要在 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
而不是其他原因引起的。
另一种方法是获取锁以防止竞争条件。由于它的数据库 table 是共享资源,您需要一个 table 锁以确保在计算工作订单数量和保存时没有其他进程正在使用工作订单 table记录。
假设您使用的是 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
使用此模式获取 table 锁将阻止其他数据库连接对 table 的所有访问。它会在事务提交时释放,但如果 ruby 进程在它有机会完成事务块之前被终止,则可能再次导致死锁。