MongoDB - 更新集合中所有记录的最快方法是啥?

Posted

技术标签:

【中文标题】MongoDB - 更新集合中所有记录的最快方法是啥?【英文标题】:MongoDB - what is the fastest way to update all records in a collection?MongoDB - 更新集合中所有记录的最快方法是什么? 【发布时间】:2010-11-10 16:09:09 【问题描述】:

我有一个包含 900 万条记录的集合。我目前正在使用以下脚本来更新整个集合:

simple_update.js

db.mydata.find().forEach(function(data) 
  db.mydata.update(_id:data._id,$set:pid:(2571 - data.Y + (data.X * 2572)));
);

这是从命令行运行的,如下所示:

mongo my_test simple_update.js

所以我所做的只是根据简单的计算添加一个新字段 pid

有没有更快的方法?这需要大量时间。

【问题讨论】:

【参考方案1】:

您可以做两件事。

    发送将“multi”标志设置为 true 的更新。 在服务器端存储函数并尝试使用server-side code execution。

该链接还包含以下建议:

这是执行批处理管理工作的好方法。在服务器上运行 mongo,通过 localhost 接口连接。然后连接非常快且延迟低。这比 db.eval() 更友好,因为 db.eval() 会阻止其他操作。

这可能是最快的。您必须意识到在单个服务器上发布 9M 更新将是一项繁重的操作。假设您每秒可以获得 3k 次更新,您仍然在谈论运行近一个小时。

这并不是真正的“mongo 问题”,而是硬件限制。

【讨论】:

那么拥有多个实例(从/主)会使其更快吗? 主/从不会提高您的写入时间。 Mongo 只有一个写入线程,并且在进行这样的大规模更新时通常会受到磁盘吞吐量的限制。您需要的“多个实例”是分片。使用分片,您将拥有两台带有两个独立磁盘的机器,并且您将获得近一倍的写入吞吐量。不过,请再次查看您的硬件并将其与您的预期吞吐量进行比较。 好的。我明白那个。读书呢?分片也是一种加快阅读或查询速度的方法吗? 拥有良好阅读时间的最佳方法是将所有内容保存在内存中。这意味着拥有尽可能多的 RAM。分片是“添加更多 RAM”的合理方式,因为您可以使用多台机器的 RAM。从这个意义上说,分片“更快”。 如果您查看原始查询,它的编写方式是每个文档都必须通过网络传输到客户端。因此,通过直接连接在服务器上运行它意味着您不必在写入过程中发送数据。显然,仅使用“多重更新”将是最简单的。但是,对于 900 万个项目,我可能会编写一个批处理脚本以便跟踪进度。【参考方案2】:

我正在使用:db.collection.update method

// db.collection.update( criteria, objNew, upsert, multi ) // --> for reference
db.collection.update(  "_id" :  $exists : true  , objNew, upsert, true);

【讨论】:

您也可以使用 (空 BSSON 对象)作为第一个参数。使用 null 将引发错误。这是 API 中的一个不一致之处,因为其他方法接受 null 作为搜索条件(并将其解释为“匹配任何内容”),例如 find 和 findOne 函数。【参考方案3】:

我不建议将 multi: true 用于更大的数据集,因为它的可配置性较低。

使用批量插入的更好方法。

批量操作对调度程序任务非常有帮助。假设您必须每天删除超过 6 个月的数据。使用批量操作。它的速度很快,不会减慢服务器的速度。当您插入、删除或更新超过十亿个文档时,CPU、内存使用情况并不明显。我发现 multi:true 在处理数百万多个文档时会降低服务器速度(需要对此进行更多研究。)

请参阅下面的示例。它是一个 js shell 脚本,也可以作为节点程序在服务器中运行。(使用 npm 模块 shelljs 或类似的方式来实现)

将 mongo 更新到 3.2+

更新多个唯一文档的正常方式是

let counter = 0;
db.myCol.find().sort($natural:1).limit(1000000).forEach(function(document)
    counter++;
    document.test_value = "just testing" + counter
    db.myCol.save(document)
);

我尝试了 310-315 秒。更新一百万个文档需要 5 分钟以上。

我的收藏包含超过 1 亿个文档,因此其他人的速度可能会有所不同。

同样使用bulk insert是

    let counter = 0;
// magic no.- depends on your hardware and document size. - my document size is around 1.5kb-2kb
// performance reduces when this limit is not in 1500-2500 range.
// try different range and find fastest bulk limit for your document size or take an average.
let limitNo = 2222; 
let bulk = db.myCol.initializeUnorderedBulkOp();
let noOfDocsToProcess = 1000000;
db.myCol.find().sort($natural:1).limit(noOfDocsToProcess).forEach(function(document)
    counter++;
    noOfDocsToProcess --;
    limitNo--;
    bulk.find(_id:document._id).update($set:test_value : "just testing .. " + counter);
    if(limitNo === 0 || noOfDocsToProcess === 0)
        bulk.execute();
        bulk = db.myCol.initializeUnorderedBulkOp();
        limitNo = 2222;
    
);

最佳时间是 8972 毫秒。因此,平均而言,更新一百万个文档只需要 10 秒。比旧方法快 30 倍。

将代码放入 .js 文件并作为 mongo shell 脚本执行。

如果有人找到更好的方法,请更新。让我们以更快的方式使用 mongo。

【讨论】:

在 shell 中“let”不能全局使用。可以在函数中使用。所以使用“var”而不是“let”。【参考方案4】:

开始Mongo 4.2db.collection.update()可以接受一个聚合管道,最后允许基于另一个字段更新/创建一个字段;从而使我们能够在服务器端完全应用这种查询:

//  Y: 456,  X: 3 
//  Y: 3452, X: 2 
db.collection.update(
  ,
  [ $set:  pid: 
    $sum: [ 2571,  $multiply: [ -1, "$Y" ] ,  $multiply: [ 2572, "$X" ]  ]
  ],
   multi: true 
)
//  Y: 456,  X: 3, pid: 9831 
//  Y: 3452, X: 2, pid: 4263 

第一部分是匹配查询,过滤要更新的文档(本例中的所有文档)。

第二部分[ $set: pid: ... ] 是更新聚合管道(注意方括号表示使用聚合管道)。 $set 是一个新的聚合运算符,别名为$addFields。请注意pid 是如何根据同一文档中的X ($X) 和Y ($Y) 的值直接创建的。

不要忘记 multi: true ,否则只会更新第一个匹配的文档。

【讨论】:

【参考方案5】:

不确定它是否会更快,但您可以进行多次更新。只需说update where _id > 0(这对每个对象都是如此),然后将'multi'标志设置为true,它应该做同样的事情而不必遍历整个集合。

检查一下: MongoDB - Server Side Code Execution

【讨论】:

以上是关于MongoDB - 更新集合中所有记录的最快方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

整理一些MongoDB常用数据库命令

是否可以将 mongoDB 中的更新查询限制为特定限制?

使用 Mongoose 在 MongoDB 中更新许多记录的正确方法是啥

Python MongoDB 更新

Python MongoDB 更新

MongoDB常用操作--集合3