First_or_create yet ERROR:重复的键值违反了唯一约束

问题描述 投票:1回答:2

我有以下代码:

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块的情况下解决此问题?

ruby-on-rails-3 postgresql activerecord race-condition select-insert
2个回答
3
投票

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

在实现中,

Ruby似乎选择performance而不是safety”。引用"Ruby on Rails Guides"

first_or_create方法检查是否先返回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

如果这是实际的实现(?),则似乎[[完全已开放了竞争条件。另一个事务可以轻松地在第一个事务的SELECTSELECT之间进行INSERT。然后尝试使用其自己的INSERT,这将引发您报告的错误,因为在此期间第一个事务已插入行。

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


0
投票
Rails 6添加了一个新的create_or_find_by方法,该方法减轻了可能的

race condition,但有一些缺点:

    基础表必须具有使用唯一约束定义的相关列。
  • 唯一约束冲突可能仅由一个或至少少于所有给定属性触发。这意味着随后的find_by!可能找不到匹配的记录,这将引发ActiveRecord::RecordNotFound异常,而不是具有给定属性的记录。
  • 虽然我们避免了find_or_create_by中SELECT-> INSERT之间的竞争条件,但实际上我们在INSERT-> SELECT之间还有另一个竞争条件,如果这两个语句之间的DELETE由另一个客户端运行,则可以触发该竞争条件。但是对于大多数应用程序,这种情况发生的可能性要小得多。
  • 它依靠异常处理来处理控制流,这可能会稍微慢一些。
  • def create_or_find_by(attributes, &block) transaction(requires_new: true) { create(attributes, &block) } rescue ActiveRecord::RecordNotUnique find_by!(attributes) end

  • 使用您的示例:

    rating = user.recipe_ratings.create_or_find_by( recipe_id: recipe.id, delivery_id: delivery.id )

    © www.soinside.com 2019 - 2024. All rights reserved.