如何在ActiveRecord中跳过INSERT ONLY语句中的事务?

看看这个例子:

2.1.3 :001 > Stat.create! (0.1ms) BEGIN SQL (0.3ms) INSERT INTO `stats` (`created_at`, `updated_at`) VALUES ('2015-03-16 11:20:08', '2015-03-16 11:20:08') (0.4ms) COMMIT => # 

正如你所看到的那样create! 方法在无用事务中执行insert语句。 如何在这种情况下禁用转换(不在整个应用程序中禁用它们)?

这个怎么运作:

持久性模块定义create : https : //github.com/rails/rails/blob/4-2-stable/activerecord/lib/active_record/persistence.rb#L46

 def create!(attributes = nil, &block) if attributes.is_a?(Array) attributes.collect { |attr| create!(attr, &block) } else object = new(attributes, &block) object.save! object end end 

它创建一个对象并调用#save!

它没有记录在公共API中,而是调用https://github.com/rails/rails/blob/4-2-stable/activerecord/lib/active_record/transactions.rb#L290

 def save!(*) #:nodoc: with_transaction_returning_status { super } end 

此时,事务再次包含save(super),它再次位于Persistence模块: https : //github.com/rails/rails/blob/4-2-stable/activerecord/lib/active_record/persistence.rb#L141

 def save!(*) create_or_update || raise(RecordNotSaved.new(nil, self)) end 

让我们用一些新方法来解决这个问题:

 module ActiveRecord module Persistence module ClassMethods def atomic_create!(attributes = nil, &block) if attributes.is_a?(Array) raise "An array of records can't be atomic" else object = new(attributes, &block) object.atomic_save! object end end end alias_method :atomic_save!, :save! end end module ActiveRecord module Transactions def atomic_save!(*) super end end end 

也许你想用标准create! 方法,然后你需要重新定义它。 我定义了第一个可选参数:atomic ,当它出现时意味着你想使用atomic_save! 方法。

 module ActiveRecord module Persistence module ClassMethods def create_with_atomic!(first = nil, second = nil, &block) attributes, atomic = second == nil ? [first, second] : [second, first] if attributes.is_a?(Array) create_without_atomic!(attributes, &block) else object = new(attributes, &block) atomic == :atomic ? object.atomic_save! : object.save! object end end alias_method_chain :create!, :atomic end end end 

config/initializers/.rb它可以工作。

它如何在控制台上运行

 ~/rails/r41example (development) > Product.atomic_create!(name: 'atomic_create') SQL (99.4ms) INSERT INTO "products" ("created_at", "name", "updated_at") VALUES (?, ?, ?) [["created_at", "2015-03-22 03:50:07.558473"], ["name", "atomic_create"], ["updated_at", "2015-03-22 03:50:07.558473"]] => # { :id => 1, :name => "atomic_create", :created_at => Sun, 22 Mar 2015 03:50:07 UTC +00:00, :updated_at => Sun, 22 Mar 2015 03:50:07 UTC +00:00 } ~/rails/r41example (development) > Product.create!(name: 'create with commit') (0.1ms) begin transaction SQL (0.1ms) INSERT INTO "products" ("created_at", "name", "updated_at") VALUES (?, ?, ?) [["created_at", "2015-03-22 03:50:20.790566"], ["name", "create with commit"], ["updated_at", "2015-03-22 03:50:20.790566"]] (109.3ms) commit transaction => # { :id => 2, :name => "create with commit", :created_at => Sun, 22 Mar 2015 03:50:20 UTC +00:00, :updated_at => Sun, 22 Mar 2015 03:50:20 UTC +00:00 } ~/rails/r41example (development) > Product.create!(:atomic, name: 'create! atomic') SQL (137.3ms) INSERT INTO "products" ("created_at", "name", "updated_at") VALUES (?, ?, ?) [["created_at", "2015-03-22 03:51:03.001423"], ["name", "create! atomic"], ["updated_at", "2015-03-22 03:51:03.001423"]] => # { :id => 3, :name => "create! atomic", :created_at => Sun, 22 Mar 2015 03:51:03 UTC +00:00, :updated_at => Sun, 22 Mar 2015 03:51:03 UTC +00:00 } 

警告:你将失去after_rollback和after_commit回调!

注意:4.1上的方法创建! 并保存! 在模块validation中。 在Rails 4.2上有Persistence。

编辑 :也许您认为您可以获得交易已用时间。 在我的例子中,提交时间转到插入(我有一个标准的HD,我认为你有一个SSD)。

这里的问题是您想要修改类级方法的行为。 这本质上不是线程安全的,至少对于其他Stat对象的并发事务而言。 一个简单的解决方法是将实例标记为不需要事务:

 class Stat < ActiveRecord::Base attr_accessor :skip_transaction def with_transaction_returning_status if skip_transaction yield else super end end end Stat.create! skip_transaction: true 

如果您在单线程框架上运行,因此不关心在此期间暂停Stat对象的事务,您可以使用类级方法并将调用包装如下:

 class Stat < ActiveRecord::Base def self.transaction(*args) if @skip_transaction yield else super end end def self.skip_transaction begin @skip_transaction = true yield ensure @skip_transaction = nil end end end Stat.skip_transaction { Stat.create! } 

最简单的方法是手动编写INSERT语句,仍然使用ActiveRecord来执行它。 这不会禁用您编写的任何其他代码的事务。

 sql = "INSERT INTO stats (created_at, updated_at) VALUES ('2015-03-16 11:20:08', '2015-03-16 11:20:08')" ActiveRecord::Base.connection.execute(sql) 

不像上面使用亚历杭德罗的解决方案那么好,但是做到了 – 特别是如果它是一次性的,桌子不太可能改变。

我不知道这样做的好方法

在ruby 2.2上你可以做到

 stat = Stat.new stat.method(:save).super_method.call 

这不适用于pre ruby​​ 2.2(当添加了super_method时)并且只有在祖先列表中才有效,因为事务是第一个(或最后一个,具体取决于您的订购方式)来覆盖保存。 如果不是那么这个代码将跳过’错误’的保存方法。 因此,我几乎不推荐这个

你可以做点什么

 stat = Stat.new m = stat.method(:save) until m.owner == ActiveRecord::Transactions m = m.super_method end m = m.super_method 

要自动走向链,直到找到事务位,但不知道你可能跳过了什么代码。

Alejandro Babio的答案很广泛但想要解释为什么交易首先完成。

这个答案解释了交易在通话中的作用。 简而言之就是这样:

 begin transaction insert record after_save called commit transaction after_commit called 

但是如果开发人员没有注册after_save挂钩,我想知道为什么不跳过事务。 对于高延迟连接,事务可能会将总体操作时间增加3倍:/ IMO Rails需要进行优化。

Rails拒绝了这样的优化,请参阅原因: https : //github.com/rails/rails/issues/26272