postgresql First_or_create yet错误:重复键值违反唯一约束

x33g5p2x  于 2023-06-05  发布在  PostgreSQL
关注(0)|答案(2)|浏览(165)

我有以下代码:

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

ou6hu8tu

ou6hu8tu1#

这似乎源于“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

如果这是实际的实现(?),它似乎是完全开放的种族条件。另一个交易可以轻松地在第一个交易的SELECTINSERT之间进行SELECT。然后尝试自己的INSERT,这将引发您报告的错误,因为第一个事务同时插入了该行。
通过数据修改CTE,竞争条件的时间范围可以大大减少。即使是一个安全的版本也不会花费那么多。但我想他们有他们的理由。
比较一下这个安全实现:

  • 函数中的SELECT或INSERT是否容易出现争用条件?
dojqjjoe

dojqjjoe2#

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
)

相关问题