MongoDB 批量写入多个 updateOne 与 updateMany

如何解决MongoDB 批量写入多个 updateOne 与 updateMany

我在某些情况下构建了 bulkWrite 操作,其中某些文档具有相同的 update 对象,合并过滤器并使用这些过滤器发送一个 updateMany 而不是多个 {{1 }} 在同一个 updateOne 中?

在使用普通方法时,在多个 bulkWrite 上使用 updateMany 显然更好,但是对于bulkWrite,由于它是一个命令,选择一个比另一个更重要吗?

示例:

我有 20 万个文档需要更新,所有 20 万个文档总共有 10 个唯一的 updateOne 字段,所以我的选择是:

解决方案:

A) 发送一个包含 10 个 status 操作的批量写入,这些操作中的每一个都会影响 20K 文档。

B) 发送一个带有 200K updateMany 每个操作持有其过滤器和 updateOne 的单个批量写入。

正如@AlexBlex 所指出的,我必须注意使用相同的过滤器意外更新多个文档,在我的情况下,我使用 status 作为我的过滤器,因此意外更新其他文档不是我的问题情况,但在考虑 _id 选项时绝对需要注意。

谢谢@AlexBlex。

解决方法

简答:

使用 updateMany 至少快两倍,但可能会意外更新比预期更多的文档,请继续阅读以了解如何避免这种情况并获得性能优势。

长答案:

我们进行了以下实验以了解答案,步骤如下:

  1. 创建一个bankaccounts mongodb 集合,每个文档只包含一个字段(余额)。
  2. bankaccounts 集合中插入 100 万个文档。
  3. 在内存中随机排列所有 100 万个文档的顺序,以避免使用以相同序列插入的 ID 对数据库进行任何可能的优化,模拟真实场景。
  4. 从具有 0 到 100 之间的随机数的文档中为 bulkWrite 构建写入操作。
  5. 执行批量写入。
  6. 记录批量写入所用的时间。

现在,实验进入第 4 步。

在实验的一个变体中,我们构建了一个由 100 万个 updateOne 操作组成的数组,每个 updateOne 都有 filter 用于单个文档,以及其各自的 `update 对象。

>

在第二个变体中,我们构建了 100 个 updateMany 操作,每个操作包括用于 10K 文档 ID 的 filter 及其各自的 update

结果: 具有多个文档 ID 的 updateMany 比多个 updateOne 快 243%,但这不能在任何地方使用,请阅读“风险”部分以了解何时应该使用.

详情: 我们对每个变体运行了 5 次脚本,详细结果如下: 使用 updateOne:平均 51.28 秒。 使用 updateMany:平均 21.04 秒。

风险: 正如许多人已经指出的那样,updateMany 不是 updateOne 的直接替代品,因为当我们的意图是真正只更新一个文档时,它可能会错误地更新多个文档。 此方法仅在您使用唯一字段(例如 _id)或任何其他唯一字段时才有效,如果过滤器依赖于非唯一字段,则会更新多个文档并显示结果不会等价。

65831219.js

// 65831219.js
'use strict';
const mongoose = require('mongoose');
const { Schema } = mongoose;

const DOCUMENTS_COUNT = 1_000_000;
const UPDATE_MANY_OPERATIONS_COUNT = 100;
const MINIMUM_BALANCE = 0;
const MAXIMUM_BALANCE = 100;
const SAMPLES_COUNT = 10;

const bankAccountSchema = new Schema({
  balance: { type: Number }
});

const BankAccount = mongoose.model('BankAccount',bankAccountSchema);

mainRunner().catch(console.error);

async function mainRunner () {
  for (let i = 0; i < SAMPLES_COUNT; i++) {
    await runOneCycle(buildUpdateManyWriteOperations).catch(console.error);
    await runOneCycle(buildUpdateOneWriteOperations).catch(console.error);
    console.log('-'.repeat(80));
  }
  process.exit(0);
}

/**
 *
 * @param {buildUpdateManyWriteOperations|buildUpdateOneWriteOperations} buildBulkWrite
 */
async function runOneCycle (buildBulkWrite) {
  await mongoose.connect('mongodb://localhost:27017/test',{
    useNewUrlParser: true,useUnifiedTopology: true
  });

  await mongoose.connection.dropDatabase();

  const { accounts } = await createAccounts({ accountsCount: DOCUMENTS_COUNT });

  const { writeOperations } = buildBulkWrite({ accounts });

  const writeStartedAt = Date.now();

  await BankAccount.bulkWrite(writeOperations);

  const writeEndedAt = Date.now();

  console.log(`Write operations took ${(writeEndedAt - writeStartedAt) / 1000} seconds with \`${buildBulkWrite.name}\`.`);
}



async function createAccounts ({ accountsCount }) {
  const rawAccounts = Array.from({ length: accountsCount },() => ({ balance: getRandomInteger(MINIMUM_BALANCE,MAXIMUM_BALANCE) }));
  const accounts = await BankAccount.insertMany(rawAccounts);

  return { accounts };
}

function buildUpdateOneWriteOperations ({ accounts }) {
  const writeOperations = shuffleArray(accounts).map((account) => ({
    updateOne: {
      filter: { _id: account._id },update: { balance: getRandomInteger(MINIMUM_BALANCE,MAXIMUM_BALANCE) }
    }
  }));

  return { writeOperations };
}

function buildUpdateManyWriteOperations ({ accounts }) {
  shuffleArray(accounts);
  const accountsChunks = chunkArray(accounts,accounts.length / UPDATE_MANY_OPERATIONS_COUNT);
  const writeOperations = accountsChunks.map((accountsChunk) => ({
    updateMany: {
      filter: { _id: { $in: accountsChunk.map(account => account._id) } },MAXIMUM_BALANCE) }
    }
  }));

  return { writeOperations };
}


function getRandomInteger (min = 0,max = 1) {
  min = Math.ceil(min);
  max = Math.floor(max);
  return min + Math.floor(Math.random() * (max - min + 1));
}

function shuffleArray (array) {
  let currentIndex = array.length;
  let temporaryValue;
  let randomIndex;

  // While there remain elements to shuffle...
  while (0 !== currentIndex) {

    // Pick a remaining element...
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex -= 1;

    // And swap it with the current element.
    temporaryValue = array[currentIndex];
    array[currentIndex] = array[randomIndex];
    array[randomIndex] = temporaryValue;
  }

  return array;
}

function chunkArray (array,sizeOfTheChunkedArray) {
  const chunked = [];

  for (const element of array) {
    const last = chunked[chunked.length - 1];

    if (!last || last.length === sizeOfTheChunkedArray) {
      chunked.push([element]);
    } else {
      last.push(element);
    }
  }
  return chunked;
}

输出

$ node 65831219.js
Write operations took 20.803 seconds with `buildUpdateManyWriteOperations`.
Write operations took 50.84 seconds with `buildUpdateOneWriteOperations`.
----------------------------------------------------------------------------------------------------

使用 MongoDB 版本 4.0.4 运行测试。

,

在高层次上,如果您有相同的更新对象,那么您可以执行 updateMany 而不是 bulkWrite

原因:

bulkWrite 旨在向服务器发送多个不同的命令here

如果您有相同的更新对象,updateMany 最适合。

性能:

如果您在bulkWrite中有10k个更新命令,它将在内部以批处理方式执行。可能会影响执行时间

来自有关批处理的参考的确切行:

每组操作最多可以有 1000 个操作。如果组超过此限制,MongoDB 会将组分成 1000 或更少的更小的组。例如,如果批量操作列表包含 2000 个插入操作,则 MongoDB 创建 2 个组,每个组有 1000 个操作。

谢谢@Alex

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-