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)
。 看到
- https://docs.mongodb.com/v3.2/reference/method/db.collection.bulkWrite/
- https://api.mongodb.com/ruby/2.4.0/Mongo/Collection.html#bulk_write-instance_method
需要两个文件来理解构建呼叫。 例如
update = TestCase.collection.bulk_write( [ replace_one: { filter: {}, update: {}, upsert: true }, update_one: { ... }, ... ], ordered: false )