我正在努力锁定我正在处理的 PostgreSQL 表。理想情况下,我想锁定整个表,但只要它们实际工作,个别行就可以。
我有几个并发的 ruby 脚本,它们都查询 AWS 上的中央作业数据库(通过 DatabaseAccessor
类),找到一个尚未开始的作业,将状态更改为 开始
并执行它。问题是,由于这些都是同时运行的,它们通常会同时找到相同的未开始工作,并开始执行它,这既浪费时间又混淆了结果。
我已经尝试了很多东西,.lock
、.transaction
、fatalistic
gem,但它们似乎不是工作,至少,不是偷偷摸摸的。
我的代码如下:
class DatabaseAccessor
require 'pg'
require 'pry'
require 'active_record'
class Jobs < ActiveRecord::Base
enum status: [ :unstarted, :started, :slow, :completed]
end
def initialize(db_credentials)
ActiveRecord::Base.establish_connection(
adapter: db_credentials[:adapter],
database: db_credentials[:database],
username: db_credentials[:username],
password: db_credentials[:password],
host: db_credentials[:host]
)
end
def find_unstarted_job
job = Jobs.where(status: 0).limit(1)
job.started!
job
end
end
有人有什么建议吗?
编辑:看来 LOCK TABLE jobs IN ACCESS EXCLUSIVE MODE;
是执行此操作的方法 - 但是,我正在努力返回结果更新后。 RETURNING *
将在更新后返回结果,但不在事务内。
最佳答案
已解决!
所以这里的关键是在 Postgres 中锁定。有几种不同的表级锁,详解here .
这里有三个因素来做出决定:
- 读取不是线程安全的。读取同一条记录的两个线程将导致该作业同时运行多次。
- 记录仅更新一次(标记为已完成)和创建,而不是从初始读取和更新开始。创建新记录的脚本不会读取该表。
- 阅读的频率各不相同。等待解锁并不重要。
考虑到这些因素,如果有一个仍然允许写入的读锁,这是可以接受的,但是,没有,所以 ACCESS EXCLUSIVE
是我们最好的选择。
鉴于此,我们如何处理锁定?翻阅 ActiveRecord 文档没有提及它。
值得庆幸的是,存在其他处理 PostgreSQL 的方法,即 ruby-pg
gem。稍后玩 SQL,测试锁定,我得到以下方法:
def converter
result_hash = {}
conn = PG::Connection.open(:dbname => 'my_db')
conn.exec("BEGIN WORK;
LOCK TABLE jobs IN ACCESS EXCLUSIVE MODE;")
conn.exec("UPDATE jobs SET status = 1 WHERE id =
(SELECT id FROM jobs WHERE status = 0 ORDER BY ID LIMIT 1)
RETURNING *;") do |result|
result.each { |row| result_hash = row }
end
conn.exec("COMMIT WORK;")
result_hash.transform_keys!(&:to_sym)
end
这将导致:
如果没有
的作业,则输出空哈希值status
为0
如果找到并更新了一个符号化散列的输出
如果数据库当前处于锁定状态,则休眠,解锁后返回上述内容。
该表将保持锁定状态,直到 COMMIT WORK
语句。
顺便说一句,我希望有一种更简洁的方法将结果转换为散列。如果有人有任何建议,请在评论中告诉我! :)
关于ruby - ActiveRecord Postgres 数据库未锁定 - 获取竞争条件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50308730/