Rails Thread.join 之后 ActiveRecord 模型与数据库不同步

Rails ActiveRecord models out-of-sync with database after Thread.join

假设我有一个名为 Person 的 ActiveRecord 模型,其 table 名称为 people,具有 idname 列。 name 列上有一个唯一键约束,因此没有两个 Person 记录可以共享一个名称。我正在使用 find_or_create_by_name 加上救援块,以防两个线程尝试插入相同的值:

def get_or_make_person(name)
  begin
    person = Person.find_or_create_by_name(name)
  rescue ActiveRecord::RecordNotUnique
    retry
  end
  person
end

我正在尝试测试我的代码的线程安全性。所以我写了一个测试:

name = 'Joe'

t1 = Thread.new do
  person = get_or_make_person name
end

t1 = Thread.new do
  person = get_or_make_person name
end

# Wait until both threads are done
t1.join
t2.join

# At this point, the person should be created no matter what. Let's find him:
person = Person.find_by_name name
assert_not_nil person

但是,这个测试失败了。加入两个线程后,我使用 Pry 检查我的程序,发现主线程中的 Person 模型对其他两个线程中创建的记录一无所知。此时手动查看数据库,肯定是在数据库中:

mysql> select * from people;
+----+------+
| id | name |
+----+------+
| 1  | Joe  |
+----+------+

我什至尝试使用 Person.find_by_sql('select * from people'),但这也没有 return 记录。

一旦我退出调试会话并使用不同的名称再次尝试测试(比如 'Jane'),我就能够使用 Person.find_by_name('Joe')Person.find(1) 检索第一条记录.

这是怎么回事?有没有办法以某种方式强制 ActiveRecord 重新加载其对 people table?

的了解

我的用例比这个稍微复杂一些(这就是为什么这个例子看起来有点做作),但这应该抓住了我运行正在研究的本质。

其他详细信息:

如果 运行 在独立脚本中,完全相同的代码 运行 成功并产生预期结果(当然使用打印语句而不是 assert_not_nil ),但失败时运行 使用 Test::Unit 作为单元测试。

我才意识到这已经在this question, with a solution found in this answer中解释过了。本质上,每个测试用例都在一个事务中执行,但线程在该事务之外创建它们的记录。关闭此测试用例的事务对我有用:

class PersonTest < ActiveSupport::TestCase
  self.use_transactional_fixtures = false
  # ...
end