我有以下代码:
rating = user.recipe_ratings.where(:recipe_id => recipe.id).where(:delivery_id => delivery.id).first_or_create
然而,不知何故,我们会从中得到偶尔的PG::Error: ERROR: duplicate key value violates unique constraint
错误。我想不出任何原因会发生,因为first_or_create
的全部目的就是为了防止这些。
这只是一个疯狂的比赛条件吗?如果没有一系列令人抓狂的begin...rescue
块,我该如何解决这个问题?
2条答案
按热度按时间ou6hu8tu1#
这似乎源于“SELECT或INSERT”情况下的典型竞争条件。
Ruby在实现中似乎选择了 * 性能 * 而不是 * 安全 *。引用“Ruby on Rails指南”:
first_or_create
方法检查first是否返回nil
。如果返回nil
,则调用create
。...
此方法生成的SQL如下所示:
如果这是实际的实现(?),它似乎是完全开放的种族条件。另一个交易可以轻松地在第一个交易的
SELECT
和INSERT
之间进行SELECT
。然后尝试自己的INSERT
,这将引发您报告的错误,因为第一个事务同时插入了该行。通过数据修改CTE,竞争条件的时间范围可以大大减少。即使是一个安全的版本也不会花费那么多。但我想他们有他们的理由。
比较一下这个安全实现:
dojqjjoe2#
Rails 6添加了一个新的create_or_find_by方法,它可以缓解可能的竞争条件,但有一些缺点:
find_by!
可能无法找到匹配的记录,这将引发ActiveRecord::RecordNotFound
异常,而不是具有给定属性的记录。find_or_create_by
之间的竞争条件,但实际上在INSERT-> SELECT之间还有另一个竞争条件,如果另一个客户端运行这两个语句之间的DELETE,就会触发这个竞争条件。但对于大多数应用程序来说,这是一个不太可能发生的情况。使用您的示例: