我有一些看起来像这样的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。
答案 0 :(得分:13)
基于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将根据您的数据库而有所不同。
答案 1 :(得分:7)
关于如何锁定整个表已有答案,但我相信你应该尽量避免这种情况。相反,我相信你应该给一些咨询锁。它确保同一块代码不会同时在两台机器上执行,同时仍然保持表格为其他业务开放。
它仍然使用数据库,但它不会锁定你的表。
您可以使用名为" with_advisory_lock"像这样:
Model.with_advisory_lock("ADVISORY_LOCK_NAME") do
# Your code
end
https://github.com/ClosureTree/with_advisory_lock
它不适用于SQLite。
答案 2 :(得分:6)
设置唯一约束不是黑客。这是使您的数据保持一致的因素。 顺便说一下,你还有更多的选择:
答案 3 :(得分:0)
您可以使用ActiveRecord::Locking::Pessimistic中的lock
方法。
答案 4 :(得分:-1)
嗯,从技术上来说,在访问表之前锁定表或总是锁定另一个表的记录是一样的。
所以你可能有另一个表有一个最大记录的表,在你要锁定的表中读/写之前,总是用http://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html锁定记录:
LockTable.last.with_lock do
// the things that needed for your table
end
答案 5 :(得分:-1)
您无需锁定大厅桌面,即可一次锁定一个代码。锁定一个完整的表会导致性能问题。你可以使用“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