【发布时间】:2015-01-14 23:55:54
【问题描述】:
假设我有一个名为 Person 的 ActiveRecord 模型,其表名为 people,其中包含 id 和 name 列。 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'),但也没有返回记录。
一旦我退出调试会话并使用不同的名称(例如“Jane”)再次尝试测试,我就可以使用 Person.find_by_name('Joe') 或 Person.find(1) 检索第一条记录。
这里发生了什么?有什么方法可以强制 ActiveRecord 重新加载其对 people 表的了解?
我的用例比这个稍微复杂一些(这就是为什么这个例子看起来有点做作),但这应该抓住我遇到的本质。
其他细节:
如果在独立脚本中运行完全相同的代码会成功运行并产生预期结果(当然使用打印语句而不是 assert_not_nil),但在使用 Test::Unit 作为单元测试运行时会失败。
【问题讨论】:
标签: ruby-on-rails ruby database thread-safety rails-activerecord