【问题标题】:Commit a nested transaction提交嵌套事务
【发布时间】:2019-09-27 00:13:09
【问题描述】:

假设我有一个方法可以在用户范围内提供对 API 客户端的访问,并且 API 客户端会在用户 OAuth 令牌过期时自动更新它们。

class User < ActiveRecord::Base

  def api
    ApiClient.new access_token: oauth_access_token,
                  refresh_token: oauth_refresh_token,
                  on_oauth_refresh: -> (tokens) {
                    # This proc will be called by the API client when an 
                    # OAuth refresh occurs
                    update_attributes({
                      oauth_access_token: tokens[:access_token],
                      oauth_refresh_token: tokens[:refresh_token]
                     })
                   }
  end

end

如果我在 Rails 事务中使用此 API 并发生刷新,然后发生错误 - 我无法保留新的 OAuth 令牌(因为上面的 proc 也被视为事务的一部分):

u = User.first

User.transaction { 
  local_info = Info.create!

  # My tokens are expired so the client automatically
  # refreshes them and calls the proc that updates them locally.
  external_info = u.api.get_external_info(local_info.id)

  # Now when I try to locally save the info returned by the API an exception
  # occurs (for example due to validation). This rolls back the entire 
  # transaction (including the update of the user's new tokens.)
  local_info.info = external_info 
  local_info.save!
}

我正在简化示例,但基本上 API 的使用和 API 返回的数据的持久性需要在事务中发生。即使父事务失败,我如何确保对用户令牌的更新得到提交。

【问题讨论】:

  • 把它放在事务之外,不是吗?
  • 不幸的是,没有。 “嵌套事务”由我无法控制的 API 客户端调用。想象一下,在我的交易中,我需要使用一个 API,而我的 OAuth 令牌过期了。 API 客户端刷新了我的令牌,但无法持久保存它们,因为下游事务失败。
  • 也许你需要Autonomous TransactionsThis article 可以帮助您掌握这个概念并将其应用到您的 Ruby 场景中。
  • 感谢您的链接。你知道任何可以帮助解决这个问题的 Ruby/Rails 抽象层吗?我宁愿避免编写存储过程。
  • 没有。 Ruby 不在我的工具集中。

标签: ruby-on-rails postgresql activerecord


【解决方案1】:

你有没有试过在新线程中打开一个新的数据库连接,并在这个线程中执行更新

u = User.first

User.transaction { 
   local_info = Info.create!

   # My tokens are expired so the client automatically
   # refreshes them and calls the proc that updates them locally.
   external_info = u.api.get_external_info(local_info.id)

   # Now when I try to locally save the info returned by the API an exception
   # occurs (for example due to validation). This rolls back the entire 
   # transaction (including the update of the user's new tokens.)
   local_info.info = external_info 
   local_info.save!

   # Now open new thread
   # In the new thread open new db connection, separate from the one already opened
   # In the new connection execute update only for the tokens
   # Close new connection and new thread
   Thread.new do
      ActiveRecord::Base.connection_pool.with_connection do |connection|
         connection.execute("Your SQL statement that will update the user tokens")        
      end
   end.join
}

希望对你有帮助

【讨论】:

  • 效果很好 - 非常感谢!实际上,您可以将事务包装在新的线程中,它就可以工作。
【解决方案2】:

Nermin 的(接受的)答案是正确的。这是 Rails >= 5.0 的更新

Thread.new do
  Rails.application.executor.wrap do
    record.save
  end
  # Note: record probably won't be updated here yet since it's async
end

在此处记录:Rails guides threading and concurrency

【讨论】:

    【解决方案3】:

    上一个问题中的discussion 可能会对您有所帮助。看起来您可以设置一个requires_new: true 标志并将子事务实质上标记为子事务。

    User.transaction { 
      User.transaction(requires_new: true) { 
        u.update_attribute(:name, 'test') 
      }; 
    
      u.update_attribute(:name, 'test2'); 
    
      raise 'boom' 
    }
    

    【讨论】:

    • 感谢您的评论。我确实尝试过,但它看起来与我需要的相反。据我了解,如果嵌套事务遇到异常而不是相反,这将提交第二次更新。
    猜你喜欢
    • 1970-01-01
    • 2011-11-20
    • 2020-05-11
    • 2015-07-31
    • 2011-06-04
    • 2021-10-09
    • 2021-08-26
    • 1970-01-01
    • 2015-10-02
    相关资源
    最近更新 更多