【问题标题】:Handling unique constraint exceptions inside transaction处理事务内的唯一约束异常
【发布时间】:2018-12-04 17:12:46
【问题描述】:

我有两个 Rails 模型/表,我想将它们作为事务的一部分插入。

例如,这里大致是我的表格:

  • 帖子:(id)
  • 评论:(id、post_id、comment_text、user_id)
  • 评论者:(id,post_id,user_id),对(post_id, user_id)

现在我正在尝试大约相当于:

ActiveRecord::Base.transaction do
  Comment.create!(post: post, user: user, comment_text: '...')

  begin
    Commenters.find_or_create_by!(post: post, user: user)
  rescue PG::UniqueViolation
  end
end

这在 99.9% 的时间都有效,但有时两个并发的 cmets 会触发 PG::UniqueViolation。

即使我正在捕获并抑制 PG::UniqueViolation,整个事务仍因以下原因而失败:

错误:当前事务被中止,命令被忽略,直到事务块结束

我意识到我已经可以通过加入 Post 和 Comment 表来实现这一点,但这是一个简化的示例。

有没有更简单的方法来确保两个插入都作为事务的一部分发生,同时仍然忽略唯一的违规,因为我们可以假设记录已经存在?

【问题讨论】:

    标签: ruby-on-rails ruby postgresql


    【解决方案1】:

    事务内部引发的异常有两件事:

    1. 回滚事务,因此事务内部所做的任何更改都不会保留在数据库中。
    2. 异常传播到事务块之外。

    因此您可以将异常处理程序移到transaction 调用之外:

    begin
      ActiveRecord::Base.transaction do
        Comment.create!(post: post, user: user, comment_text: '...')
        Commenters.find_or_create_by!(post: post, user: user)
      end
    rescue PG::UniqueViolation
      retry
    end
    

    如果您想要更安全,您可以包含一个仅重试几次的计数器。

    【讨论】:

      【解决方案2】:

      您应该在模型中正确设置关联,因此 rails 会为您进行验证。然后你可以简单地拯救可能的ActiveRecord::RecordInvalid(带有消息Validation failed: 属性 has already been taken)。

      如果您想了解更多关于唯一性验证的信息,这应该会派上用场:http://guides.rubyonrails.org/active_record_validations.html#uniqueness

      在我看来,您的Commenter 模型实际上并不需要。它只包含派生信息,这些信息也可以直接从Comments 模型中提取(您已经存储了post_iduser_id),因此您可以完全删除Commenter 类。然后注意在创建时验证Comment,例如设置

      class Comment
       belongs_to :post
       belongs_to :user
       validates :user_id, uniqueness: {scope: :post_id}
      end
      

      但是这样你将只允许用户评论一次。

      我建议您放弃唯一性约束,并通过在 Comment 模型上进行 distinct 选择来构建存储在 Commenter 中的信息。

      PS:rails 中的模型用大写和单数编写,而表(通过符号)用小写和复数表示。

      【讨论】:

      • 对,如前所述,这不是实际代码,只是一个类似的示例。我认为 Rails 验证在这里没有帮助,因为由于并发性,仍然可能引发异常:api.rubyonrails.org/v5.0/classes/ActiveRecord/Validations/…
      • 您可能需要更仔细地阅读您链接到的指南,尤其是“您必须在数据库中的该列上创建唯一索引”。唯一性验证永远不够。
      • 是的,这正是它的设置方式。由于并发请求,唯一索引/约束是引发问题中提到的 PG:UniqueViolation 的原因。
      【解决方案3】:

      错误本身是由于您的应用程序的多线程行为而发生的。

      您需要挽救 ActiveRecord::RecordNotUnique 而不是 PG 特有的错误。

      也可能将事务放在开始救援结束块中。 并重试以继续救援块内的另一个事务。类似于建议的另一个答案。

      【讨论】:

        猜你喜欢
        • 2017-05-16
        • 2019-03-08
        • 1970-01-01
        • 1970-01-01
        • 2018-08-14
        • 2021-03-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多