Mongoid批量更新/ Upsert替代?

我知道Mongoid v3 +支持通过Model.collection.insert()批量插入。 但是,我不认为它支持批处理更新,其中每个记录的属性不同(所以我不认为update_all也可以工作)。 有没有办法进行批量更新/ upsert而不是单记录查找和更新?

这是一个简化的例子,我有2个模型:

 class Product ... has_and_belongs_to_many :lists end class List ... has_and_belongs_to_many :products end 

创建新Product ,我将其与一个或多个Lists相关联。 但是,我还需要每天更新Product属性而不会丢失List参考信息(我确实没有运行Product上的validation)。

不使用批处理的一种方法是在Product上调用find_or_initialize_by并更新属性。 但对10K-1M +记录这样做是非常耗时的。

另一种使用批量插入的方法是执行Product.delete_all ,然后执行Product.collection.insert(...) ,但这会创建新的product_ids并且不再维护与List的关系。

在这个例子中有没有办法进行批量更新或upsert?

MongoDB 2.6支持update命令,请参阅http://docs.mongodb.org/manual/reference/command/update/

Mongoid.default_session为您提供MongoDB数据库级别的command方法的相应访问权限。 以下示例说明如何构造和发出应该回答您的问题的批量更新命令。 输出显示10个列表,单个与批量更新运行时间的比较,显示更新的库存计数的Product集合的转储以及批量更新命令的结构。 即使在这个简单的演示中,批量更新与单个更新相比,性能也有显着提升。

Ruby’mongo’1.x驱动程序支持用于批量写入的流畅API,新的Ruby’mongo’2.0驱动程序也将支持它,最终将进入Mongoid。 Mongoid v3 +目前使用的Moped驱动程序没有流畅的API进行批量写入。 但正如此处所示,通过Session#命令方法构造并直接在Mongoid / Moped中发出批量写入(批量更新)命令是相当简单的。

希望这会有所帮助。

应用程序/模型/ product.rb

 class Product include Mongoid::Document field :name, type: String field :stock, type: Integer, default: 0 has_and_belongs_to_many :lists, index: true end 

应用程序/模型/ list.rb

 class List include Mongoid::Document field :name, type: String has_and_belongs_to_many :products end 

测试/单元/ product_test.rb

 require 'test_helper' require 'benchmark' require 'pp' class ProductTest < ActiveSupport::TestCase def setup @session = Mongoid.default_session @session.drop end test '0. mongoid version' do puts "\nMongoid::VERSION:#{Mongoid::VERSION}\nMoped::VERSION:#{Moped::VERSION}" end def individual_updates(list_ids, repetitions) repetitions.times do list_ids.each_with_index do |list_id, index| Product.where(:list_ids => list_id).update_all({'$inc' => {'stock' => index}}) end end end def bulk_command(list_ids, repetitions) updates = [] repetitions.times do list_ids.each_with_index do |list_id, index| updates << {'q' => {'list_ids' => list_id}, 'u' => {'$inc' => {'stock' => index}}, 'multi' => true} end end { update: Product.collection_name.to_s, updates: updates, ordered: false } end def bulk_updates(list_ids, repetitions) @session.command(bulk_command(list_ids, repetitions)) end test 'x' do puts [ ['ASUS MeMO Pad FHD 10', ['ASUS', 'tablet', 'Android']], ['Apple iPad Air Wi-Fi + Cellular 128GB - Silver', ['Apple', 'tablet', 'iOS']], ['Apple iPad mini with Retina display Wi-Fi + Cellular 128GB - Silver', ['Apple', 'tablet', 'iOS']], ['Apple iPhone 5c 32GB Green', ['Apple', 'phone', 'iOS']], ['Apple iPhone 5s 64GB Space Gray', ['Apple', 'phone', 'iOS']], ['LG G Pad 8.3 Tablet', ['LG', 'tablet', 'Android']], ['LG Google Nexus 5 White', ['LG', 'phone', 'Android']], ['Microsoft Surface 7ZR-00001', ['Microsoft', 'tablet', 'Windows 8 RT']], ['Samsung Galaxy S4 I9500', ['Samsung', 'phone', 'Android']], ['Samsung Galaxy Tab S 8.4', ['Samsung', 'tablet', 'Android']] ] .each do |product_name, list_names| product = Product.create(name: product_name) list_names.each do |list_name| list = List.where(name: list_name).first_or_create list.products << product end end list_names = List.all.to_a.collect(&:name).sort.uniq p list_names list_ids = list_names.collect{|list_name| List.where(name: list_name).first.id} assert(list_ids.count > 0) Benchmark.bm(20) do |x| x.report('individual updates') { individual_updates(list_ids, 100) } x.report('bulk updates') { bulk_updates(list_ids, 100) } end pp Product.all.to_a db_command = bulk_command(list_ids, 1) assert(db_command[:updates].size > 0) pp db_command end end 

耙试验

 Run options: # Running tests: [1/2] ProductTest#test_0._mongoid_version Mongoid::VERSION:3.1.6 Moped::VERSION:1.5.2 [2/2] ProductTest#test_x ["ASUS", "Android", "Apple", "LG", "Microsoft", "Samsung", "Windows 8 RT", "iOS", "phone", "tablet"] user system total real individual updates 0.420000 0.070000 0.490000 ( 0.489579) bulk updates 0.060000 0.000000 0.060000 ( 0.180926) [#, #, #, #, #, #, #, #, #, #] {:update=>"products", :updates=> [{"q"=>{"list_ids"=>"5408b72c7f11bad1ca000002"}, "u"=>{"$inc"=>{"stock"=>0}}, "multi"=>true}, {"q"=>{"list_ids"=>"5408b72c7f11bad1ca000004"}, "u"=>{"$inc"=>{"stock"=>1}}, "multi"=>true}, {"q"=>{"list_ids"=>"5408b72c7f11bad1ca000006"}, "u"=>{"$inc"=>{"stock"=>2}}, "multi"=>true}, {"q"=>{"list_ids"=>"5408b72c7f11bad1ca00000d"}, "u"=>{"$inc"=>{"stock"=>3}}, "multi"=>true}, {"q"=>{"list_ids"=>"5408b72c7f11bad1ca000010"}, "u"=>{"$inc"=>{"stock"=>4}}, "multi"=>true}, {"q"=>{"list_ids"=>"5408b72c7f11bad1ca000013"}, "u"=>{"$inc"=>{"stock"=>5}}, "multi"=>true}, {"q"=>{"list_ids"=>"5408b72c7f11bad1ca000011"}, "u"=>{"$inc"=>{"stock"=>6}}, "multi"=>true}, {"q"=>{"list_ids"=>"5408b72c7f11bad1ca000007"}, "u"=>{"$inc"=>{"stock"=>7}}, "multi"=>true}, {"q"=>{"list_ids"=>"5408b72c7f11bad1ca00000a"}, "u"=>{"$inc"=>{"stock"=>8}}, "multi"=>true}, {"q"=>{"list_ids"=>"5408b72c7f11bad1ca000003"}, "u"=>{"$inc"=>{"stock"=>9}}, "multi"=>true}], :ordered=>false} Finished tests in 1.334821s, 1.4983 tests/s, 1.4983 assertions/s. 2 tests, 2 assertions, 0 failures, 0 errors, 0 skips 

也许加里的回答很有效。 但我发现mongoid 5.x和6.x使用标准的mongodb ruby​​驱动程序,可以做Model.mongo_client.bulk_write([op1, op2, ...], options) 。 看到

需要两个文件来理解构建呼叫。 例如

  update = TestCase.collection.bulk_write( [ replace_one: { filter: {}, update: {}, upsert: true }, update_one: { ... }, ... ], ordered: false )