如何使用mongoid/mongodb进行批量更新/插入操作?

4

我有一个包含数百万个订单文档的数据库。我使用以下方法批量插入它们:

Order.collection.insert([
                         {:_id=>BSON::ObjectId('5471944843687229cdfb0000'), :status=>"open", :name=> "Benny"},
                         {:_id=>BSON::ObjectId('5471944843687229cdfc0000'), :status=>"open", :name=> "Allan"}
                        ])

我经常需要更新订单上的status属性。使用update_attribute方法逐个更新它们效率太低。

如何批量更新多个MongoDB文档?

以下“虚构”的代码最能描述所需的解决方案:

# IMPORTANT: The exemplified upsert method does not exist

Order.collection.upsert([
                         {:_id=>BSON::ObjectId('5471944843687229cdfb0000'), :status=>"closed"},
                         {:_id=>BSON::ObjectId('5471944843687229cdfc0000'), :status=>"some_other_status"}
                        ])

提醒一下,这个SO帖子可能有一个类似的问题/答案,但说实话我并不理解那个答案。


链接问题中的答案给出了一个很长的例子,但是你需要理解(并从中倒推)的关键行是这个:"{ update: Product.collection_name.to_s, updates: updates, ordered: false }"。它是更新命令,可以接受多个更新指令。"updates"参数是要进行更新的更新列表 - 向后查看代码,看看如何构建这批更新。 - Asya Kamsky
嗨@AsyaKamsky,你能把它作为一个更短更简洁的答案放在这个问题中吗?我只有两天时间来奖励答案。 - Cjoerg
可能是Mongoid批量更新/插入替代方案?的重复问题。 - akostadinov
4个回答

6
参考问题中最好的答案可以简化为:
id_status = [['5471944843687229cdfb0000','closed'], ...] 

bulk_order = id_status.map do |id, status| # Using array destructuration
  { update_one:
    {
      filter: { _id: id },
      update: { :'$set' => {
        status: status,
      }}
    }
  }
end
YourCollection.collection.bulk_write(bulk_order)

3
首先,您需要过滤 Orders 中与 orders_to_update 匹配的 id。您可以使用 any_in Criteria 方法 进行过滤。然后使用 update_all 批量更新它们。
操作步骤如下:
orders_to_update = [BSON::ObjectId('5471944843687229cdfb0000'), BSON::ObjectId('5471944843687229cdfc0000')]

Order.any_in(id: orders_to_update).update_all(status: "closed")

1
@Anzeo:基本上,我们使用“any_in”条件方法(http://two.mongoid.org/docs/querying/criteria.html#any_in)仅过滤与“orders_to_update”匹配的订单,并使用“update_all”(http://mongoid.org/en/mongoid/docs/querying.html)批量更新它们。 - borjagvo
1
谢谢您的答案。尽管我示例中的所有文档都应更新为相同的值(closed),但我的“虚构”示例要求提供一个现实生活解决方案,可以指定每个文档要更新的内容。例如,其中一个文档可以使用值closed进行更新,而另一个文档则可以使用refunded进行更新。 - Cjoerg
@ChristofferJoergensen - 处理这些更新是否可行?换句话说,批量更新所有应更改为“已关闭”的内容,然后批量更新所有应更改为“已退款”的内容。如果不行,那么您如何确定每个文档的状态应该是什么?我希望这些信息可以帮助修改答案。 - KingOfTheNerds
非常感谢您的提问。但是,在更新字段为关系(因此所有值都是唯一的)的情况下,这并不会节省时间。据我所见,唯一的可能性是能够定义一个MongoDB ID数组,以及字段名称和字段值。 - Cjoerg

1

这里的真正问题是更新。更新很慢,因为它需要读取、替换和更改文档。

我被同样的问题阻塞了很多天。我在stackoverflow和其他网站上都没有找到任何解决方案。因此,我编写了自己的解决方案。也许你会觉得它不是很“干净”,但它可以以优秀的时间结果工作。

解决方案是销毁并重新创建此文档。销毁非常快,使用批量执行“collection.insert”创建新文档也非常快。

def get_orders(*params)
   Order.where(# some conditions).asc(:id)
end

namespace :my_collection_repairer do
desc ""

  task update: :environment do
    all_orders = get_orders(# some conditions)
    while all_orders.count > 0
      num_docs = all_orders.count
      group_size = 10000
      num_groups = (Float(num_docs) / group_size).ceil
      puts "#{num_docs} documents found. #{num_groups} groups calculated."

      1.upto(num_groups) do |group|
        updated_order_list = []
        order_group = all_orders.page(group).per(group_size)
        puts "group #{group}"

        order_group.each do |order|
          updated_order = update_order(order) # this represents your custom update method
          updated_order_list << updated_order.as_document
          order.destroy
        end

        Order.collection.insert(updated_order_list)
        puts "Group #{group} updated."
      end
      all_orders = get_orders(# some conditions)
    end
  end
end

-1

为更新或替换操作设置upsert选项,并具有以下语法

 bulk.find( { status: "closed" } ).update( { $set: { status: "some_other_status" } } );
 bulk.execute();

向批量操作列表添加多个更新操作。该方法会更新现有文档中的特定字段。

使用Bulk.find()方法指定确定要更新哪些文档的条件。 Bulk.find.update()方法将更新所有匹配的文档。如需指定单个文档更新,请参阅Bulk.find.updateOne()

 var bulk = db.collection.initializeUnorderedBulkOp();
 bulk.find( { status: "closed" } ).upsert().update(
{
 $set: { status: "some_other_status"}
}
);
bulk.execute();

注意:

要为此操作指定 upsert: true,请使用 Bulk.find.upsert()。 使用 Bulk.find.upsert(),如果没有文档与 Bulk.find() 查询条件匹配,则更新操作仅插入单个文档。 希望这可以帮到你。


谢谢@SUNDARRAJANK。但是你能否编辑或添加示例,使其使用与问题中相应的示例值?我很难理解Bulk等代表什么。 - Cjoerg
尽管我的示例中所有文档都应更新为相同的值(“closed”),但我的“虚构”示例要求一种实际解决方案,我可以指定每个文档要更新的内容。例如,其中一个文档可以使用值closed进行更新,而另一个文档可以使用refunded进行更新。 - Cjoerg
嗨,@SUNDARRAJANK,我又来了。抱歉,但我没有理解最后一条评论。 - Cjoerg
我很想理解这个答案,但是我不明白。你能否编辑一下答案,让它使用与问题中相同的变量/值?我不理解MongoDB语法。而且问题是在Ruby上下文中的,所以我需要答案也是如此。 - Cjoerg
他提到了一个mongoDB查询,就我所知,Ruby on Rails甚至不支持批量插入。 - user1735921
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接