【问题标题】:First_or_create yet ERROR: duplicate key value violates unique constraintFirst_or_create yet 错误:重复键值违反唯一约束
【发布时间】:2014-10-11 21:35:11
【问题描述】:

我有以下代码:

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 块,我该如何解决这个问题?

【问题讨论】:

  • 完全不要使用first_or_create,手动操作。即使您自己的版本在异常处理包装器中执行天真的 SELECT/INSERT 也会比 AR 的 first_or_create 更好。

标签: ruby-on-rails-3 postgresql activerecord race-condition select-insert


【解决方案1】:

这似乎源于“SELECT or INSERT”案例的典型竞态条件

Ruby 在其实现中似乎选择性能 而不是安全。引用"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

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

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

【讨论】:

  • 那有办法解决这个问题吗?
  • @hobberwickey:点击链接。
【解决方案2】:

Rails 6 添加了一个新的create_or_find_by 方法来缓解可能的竞态条件,但有一些缺点:

  • 基础表必须具有使用唯一约束定义的相关列。
  • 唯一约束违规可能仅由给定属性中的一个或至少不是全部触发。这意味着后续的 find_by!可能无法找到匹配的记录,这将引发ActiveRecord::RecordNotFound 异常,而不是具有给定属性的记录。
  • 虽然我们避免了 SELECT -> INSERT from find_or_create_by 之间的竞争条件,但实际上我们在 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
)

【讨论】:

    猜你喜欢
    • 2020-02-15
    • 2016-07-27
    • 2011-10-17
    • 2012-03-03
    • 2018-08-26
    • 2021-10-23
    • 1970-01-01
    • 1970-01-01
    • 2016-10-24
    相关资源
    最近更新 更多