首页 > 解决方案 > ActiveRecord Postgres 数据库未锁定 - 获取竞争条件

问题描述

我正在努力锁定我正在处理的 PostgreSQL 表。理想情况下,我想锁定整个表,但只要它们实际工作,个别行就可以了。

我有几个并发的 ruby​​ 脚本,它们都查询 AWS 上的中央作业数据库(通过一个DatabaseAccessor类),找到一个尚未开始的作业,将状态更改为started并执行它。问题是,由于这些都是同时运行的,它们通常会同时找到相同的未开始工作,并开始执行,浪费时间并混淆结果。

我已经尝试了很多东西.lock,,,宝石.transactionfatalistic但它们似乎没有工作,至少,没有被撬开。

我的代码如下:

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 *将在更新后返回结果,但不在事务内。

标签: rubymultithreadingpostgresqlactiverecord

解决方案


解决了!

所以这里的关键是锁定 Postgres。有几种不同的表级锁,在此处详述。

在这里做出决定有三个因素:

  1. 读取不是线程安全的。读取同一记录的两个线程将导致该作业一次运行多次。
  2. 记录仅更新一次(标记为已完成)并创建,而不是初始读取和更新开始。创建新记录的脚本不会读取该表。
  3. 阅读频率不同。等待解锁并不重要。

考虑到这些因素,如果有一个仍然允许写入的读锁,这是可以接受的,但是,没有,所以ACCESS EXCLUSIVE是我们最好的选择。

鉴于此,我们如何处理锁定?对 ActiveRecord 文档的搜索没有提及它。

值得庆幸的是,存在其他处理 PostgreSQL 的方法,即ruby-pggem。稍后用 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

这将导致:

  • 如果没有具有 astatus的作业,则输出空哈希0

  • 如果找到并更新了符号化哈希的输出

  • 如果数据库当前被锁定,则休眠,然后在解锁后返回上述内容。

该表将保持锁定状态,直到该COMMIT WORK语句。

顺便说一句,我希望有一种更简洁的方法将结果转换为哈希。如果有人有任何建议,请在评论中告诉我!:)


推荐阅读