First_or_create 尚未错误:重复的键值违反了唯一约束

Ale*_*eue 3 postgresql activerecord race-condition select-insert ruby-on-rails-3

我有以下代码:

rating = user.recipe_ratings.where(:recipe_id => recipe.id).where(:delivery_id => delivery.id).first_or_create
Run Code Online (Sandbox Code Playgroud)

然而不知何故,我们偶尔会遇到PG::Error: ERROR: duplicate key value violates unique constraint错误。我想不出任何应该发生的原因,因为重点first_or_create是防止这些情况发生。

这只是一个疯狂的竞争条件吗?我怎样才能在没有一系列令人发狂的begin...rescue块的情况下解决这个问题?

Erw*_*ter 5

这似乎源于“SELECT 或 INSERT”情况的典型竞争条件。

Ruby 在其实现中似乎更注重性能而非安全性。引用《Ruby on Rails 指南》

first_or_create方法检查first是否返回nil。如果它确实返回nil,则create调用。

...

该方法生成的 SQL 如下所示:

SELECT * FROM clients WHERE (clients.first_name = 'Andy') LIMIT 1
BEGIN
INSERT INTO clients (created_at, first_name, locked, orders_count, updated_at)
VALUES ('2011-08-30 05:22:57', 'Andy', 0, NULL, '2011-08-30 05:22:57')
COMMIT
Run Code Online (Sandbox Code Playgroud)

如果这是实际的实现(?),它似乎完全开放竞争条件。另一个交易可以很容易地SELECT在第一个交易的SELECT和 之间进行INSERT。然后尝试它自己的INSERT,这会引发您报告的错误,因为第一个事务同时插入了该行。

通过数据修改 CTE,竞争条件的时间范围可以大大缩短。即使是安全版本也不会花费那么多。但我想他们有他们的理由。
比较这个安全的实现: