Rails Thread.join 之后 ActiveRecord 模型与数据库不同步
Rails ActiveRecord models out-of-sync with database after Thread.join
假设我有一个名为 Person
的 ActiveRecord 模型,其 table 名称为 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')
,但这也没有 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
假设我有一个名为 Person
的 ActiveRecord 模型,其 table 名称为 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')
,但这也没有 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