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

时间:2015-01-14 23:55:54

标签: ruby-on-rails ruby database thread-safety rails-activerecord

假设我有一个名为Person的ActiveRecord模型,其表名为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'),但这也没有返回记录。

一旦我退出调试会话并使用其他名称再次尝试测试(说“简”),我就可以使用Person.find_by_name('Joe')Person.find(1)检索第一条记录。

这里发生了什么?有没有办法以某种方式强制ActiveRecord重新加载其people表的知识?

我的用例比这更具参与性(这就是为什么这个例子看起来有点人为),但这应该捕捉到我遇到的内容的本质。

其他详情:

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

1 个答案:

答案 0 :(得分:0)

我刚刚意识到这已经在this question中解释过,并在this answer中找到了解决方案。实质上,每个测试用例都在事务中执行,但线程在该事务之外创建其记录。关闭此测试用例的事务对我有用:

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