我知道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
上的验证未运行)。
不使用批处理的一种方法是在find_or_initialize_by
上调用Product
并更新属性。但对10K-1M +记录这样做是非常耗时的。
另一种使用批量插入的方法是Product.delete_all
然后Product.collection.insert(...)
,但这会创建新的product_ids
,并且不再维护与List
的关系。
在这个例子中有没有办法进行批量更新或upsert?
答案 0 :(得分:12)
MongoDB 2.6支持update
命令,请参阅http://docs.mongodb.org/manual/reference/command/update/
Mongoid.default_session
为您提供MongoDB数据库级别command
方法的相应访问权限。
以下示例说明如何构造和发出应该回答您的问题的批量更新命令。
输出显示10个列表,
个人与批量更新运行时间的比较,
产品集合的转储显示更新的库存计数,
以及批量更新命令的结构。
即使在这个简单的演示中,批量更新与个人更新相比也有显着的性能提升。
Ruby' mongo' 1.x驱动程序支持用于批量写入的流畅API, 这也将得到新的Ruby' mongo' 2.0驱动程序最终将进入Mongoid。 Mongoid v3 +目前使用的Moped驱动程序没有流畅的API进行批量写入。 但正如这里所示, 构造并直接发出批量写入(批量更新)命令非常简单 在Mongoid / Moped中通过Session#命令方法。
希望这会有所帮助。
应用程序/模型/ 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
rake test
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)
[#<Product _id: 5408b72b7f11bad1ca000001, name: "ASUS MeMO Pad FHD 10", stock: 2000, list_ids: ["5408b72c7f11bad1ca000002", "5408b72c7f11bad1ca000003", "5408b72c7f11bad1ca000004"]>,
#<Product _id: 5408b72c7f11bad1ca000005, name: "Apple iPad Air Wi-Fi + Cellular 128GB - Silver", stock: 3600, list_ids: ["5408b72c7f11bad1ca000006", "5408b72c7f11bad1ca000003", "5408b72c7f11bad1ca000007"]>,
#<Product _id: 5408b72c7f11bad1ca000008, name: "Apple iPad mini with Retina display Wi-Fi + Cellular 128GB - Silver", stock: 3600, list_ids: ["5408b72c7f11bad1ca000006", "5408b72c7f11bad1ca000003", "5408b72c7f11bad1ca000007"]>,
#<Product _id: 5408b72c7f11bad1ca000009, name: "Apple iPhone 5c 32GB Green", stock: 3400, list_ids: ["5408b72c7f11bad1ca000006", "5408b72c7f11bad1ca00000a", "5408b72c7f11bad1ca000007"]>,
#<Product _id: 5408b72c7f11bad1ca00000b, name: "Apple iPhone 5s 64GB Space Gray", stock: 3400, list_ids: ["5408b72c7f11bad1ca000006", "5408b72c7f11bad1ca00000a", "5408b72c7f11bad1ca000007"]>,
#<Product _id: 5408b72c7f11bad1ca00000c, name: "LG G Pad 8.3 Tablet", stock: 2600, list_ids: ["5408b72c7f11bad1ca00000d", "5408b72c7f11bad1ca000003", "5408b72c7f11bad1ca000004"]>,
#<Product _id: 5408b72c7f11bad1ca00000e, name: "LG Google Nexus 5 White", stock: 2400, list_ids: ["5408b72c7f11bad1ca00000d", "5408b72c7f11bad1ca00000a", "5408b72c7f11bad1ca000004"]>,
#<Product _id: 5408b72c7f11bad1ca00000f, name: "Microsoft Surface 7ZR-00001", stock: 3800, list_ids: ["5408b72c7f11bad1ca000010", "5408b72c7f11bad1ca000003", "5408b72c7f11bad1ca000011"]>,
#<Product _id: 5408b72c7f11bad1ca000012, name: "Samsung Galaxy S4 I9500", stock: 2800, list_ids: ["5408b72c7f11bad1ca000013", "5408b72c7f11bad1ca00000a", "5408b72c7f11bad1ca000004"]>,
#<Product _id: 5408b72c7f11bad1ca000014, name: "Samsung Galaxy Tab S 8.4", stock: 3000, list_ids: ["5408b72c7f11bad1ca000013", "5408b72c7f11bad1ca000003", "5408b72c7f11bad1ca000004"]>]
{: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
答案 1 :(得分:1)
可能Gary的回答很有效。但我在使用标准mongodb ruby驱动程序的mongoid 5.x和6.x中找到了Model.mongo_client.bulk_write([op1, op2, ...], options)
。参见
需要两个文件来理解构建呼叫。 e.g
update = TestCase.collection.bulk_write(
[
replace_one: {
filter: {},
update: {},
upsert: true
},
update_one: { ... },
...
],
ordered: false
)