谈谈Mongodb insertMany的一些坑

概述

Mongodb提供了多种方法向集合中插入数据

  • 插入一条数据

    db.collection.insertOne()

  • 插入多个文档

    db.collection.insertMany()

  • 更新集合中不存在的文档数据,指定{upsert: true}时插入数据

    db.collection.updateOne()
    db.collection.updateMany()
    db.collection.findAndModify()
    db.collection.findAndUpdate()
    db.collection.findAndReplace()
    db.collection.bulkWrite()

_id字段

mongodb插入方法中,对_id字段有一致的行为。当客户端插入数据,没有指定_id字段时,数据库自动添加一个ObjectId类型的_id字段作为主键。mongodb为集合的_id字段添加唯一键索引,因此用户插入带有_id字段的文档时,mongodb会对_id字段唯一性进行校验。数据成功插入后,mongodb返回插入文档的id

复制代码
//向集合product插入一条没有id字段的文档
db.products.insertOne( { item: "card", qty: 15})

//返回插入成功并返回插入数据的id
{
	"acknowledged" : true,
	"insertedId" : ObjectId("65966778d63bea6fd2f4b7a7")
}

//向集合product插入指定id字段的文档
db.products.insertMany([
  {_id:1, item: "card", qty: 15},
  {_id:2, item: "pen", qty: 15},
] )

//返回插入成功和插入数据的id
{
	"acknowledged" : true,
	"insertedIds" : [ 1, 2 ]
}

//向集合products插入已存_id的数据
db.products.insertMany([
  {_id:2, item: "bag", qty: 15}
] )

//报错
"writeErrors" : [
		{
			"index" : 0,
			"code" : 11000,
			"errmsg" : "E11000 duplicate key error collection: test.products index: _id_ dup key: { _id: 2 }",
			"op" : {
				"_id" : 2,
				"item" : "bag",
				"qty" : 15
			}
		}
	],

原子性

Mongodb对单文档的操作是原子性的。包括单文档的插入更新和删除操作。而插入方法中的insertMany(), updateMany(),bulkWrite()方法,操作多个文档的时,并不是原子操作。这样会产生一个问题,执行insertMany()方法,倒地插入了多少条数据。

复制代码
//这条语句,插入多少条数据?
db.products.insertMany([
  {_id:3, item: "bag", qty: 15},
  {_id:4, item: "ruler", qty: 10},
  {_id:4, item: "cup", qty: 12},
  {_id:5, item: "key", qty: 14}
] )

回顾insertMany的语法

复制代码
//insertMany的语法
db.collection.insertMany(
	[<document 1>, <document 2>, ...],
  {
    writeConcern: <document>,
    ordered:<boolean>
  }
)

参数定义

|--------------|------|----------------------------|
| 参数名 | 类型 | 描述 |
| document | 文档类型 | 计划插入的文档数组 |
| writeConcern | 文档类型 | 可选参数,指定数据提交方式,缺省使用默认数据提交方式 |
| ordered | 布尔类型 | 是否按照数组中文档的顺序插入数据,默认true |

这里writeConcern参数稍后再提,ordered参数,会对报错的insertMany()方法结果产生不同的影响。当指定ordered为true或使用默认值时。Mongodb按照数组中文档的顺序,逐一将数据插入集合。插入过程中出现错误时,插入操作停止,后面的数据不会被插入。当指定ordered为false时,数据插入过程中出错时,mongodb会继续插入后面的数据。

复制代码
db.products.insertMany([
  {_id:3, item: "bag", qty: 15},
  {_id:4, item: "ruler", qty: 10},
  {_id:4, item: "cup", qty: 12},
  {_id:5, item: "key", qty: 14}
] )

BulkWriteError({
	"writeErrors" : [
		{
			"index" : 2,
			"code" : 11000,
			"errmsg" : "E11000 duplicate key error collection: test.products index: _id_ dup key: { _id: 4 }",
			"op" : {
				"_id" : 4,
				"item" : "cup",
				"qty" : 12
			}
		}
	],
	"writeConcernErrors" : [ ],
	"nInserted" : 2,
	"nUpserted" : 0,
	"nMatched" : 0,
	"nModified" : 0,
	"nRemoved" : 0,
	"upserted" : [ ]
})

返回结果中,显示插入2条数据,其中item: "cup"的数据,因为主键冲突插入失败,而_id:5的数据,也没有插入

指定{ordered: false},重新执行插入

复制代码
db.products.insertMany([
  {_id:3, item: "bag", qty: 15},
  {_id:4, item: "ruler", qty: 10},
  {_id:4, item: "cup", qty: 12},
  {_id:5, item: "key", qty: 14}
], {
    ordered: false
} )

BulkWriteError({
	"writeErrors" : [
		{
			"index" : 2,
			"code" : 11000,
			"errmsg" : "E11000 duplicate key error collection: test.products index: _id_ dup key: { _id: 4 }",
			"op" : {
				"_id" : 4,
				"item" : "cup",
				"qty" : 12
			}
		}
	],
	"writeConcernErrors" : [ ],
	"nInserted" : 3,
	"nUpserted" : 0,
	"nMatched" : 0,
	"nModified" : 0,
	"nRemoved" : 0,
	"upserted" : [ ]
})

3条数据插入成功,只有item: "cup" 因为主键冲突,导致插入失败

这里没有使用mongodb的事务。mongodb的事务是原子性的,当在mongodb事务中插入数据报错时,数据都不会被插入。

writeConcern

现在来探讨一下writeConcern。 mongodb通过writeConcern定义了复制集中数据提交返回的方法。当指定writeConcern为majority时,需要大于一般的复制集节点数据提交完成通知主节点后,主节点才会将插入结果返回给客户端。如果从节点通知主节点的时间超过了wtimeout设定的时间,数据插入时会报replication time out error。

复制代码
//本案例可能无法再本地重现,适当减少wtimeout时间尝试
db.products.insertMany(
      [
         { _id: 10, item: "large box", qty: 20 },
         { _id: 11, item: "small box", qty: 55 },
         { _id: 12, item: "medium box", qty: 30 }
      ],
      { w: "majority", wtimeout: 100 }
   );

WriteConcernError({
   "code" : 64,
   "errmsg" : "waiting for replication timed out",
   "errInfo" : {
     "wtimeout" : true,
     "writeConcern" : {    // Added in MongoDB 4.4
       "w" : "majority",
       "wtimeout" : 100,
       "provenance" : "getLastErrorDefaults"
     }
   }
})

插入数量

每次插入操作插入文档的数量,不能超过maxWriteBatchSize的限制。maxWriteBatchSize默认值是100000. 设置这样的限制,避免了数据库插入超量报错。一些数据库连接驱动插入数据时,会按照maxWriteBatchSize将插入数据分批插入。如插入200000条数据,数据库驱动可能会分成两个插入操作,每次插入100000条。

执行计划

insertOne(), insertMany()方法不支持使用db.collection.explain()方法获取执行计划。

性能

向数据库插入大量随机数字段(如hash值),并在这些字段上有索引时,插入性能可能会变差。批量插入随机数时,构建更新索引会消耗大量的cpu和内存。因此插入这样的数据时,建议预先删除集合上的索引,插入完成后重新构建索引。或向一个没有索引的集合插入数据。

相关推荐
云边有个稻草人2 分钟前
智启未来:当知识库遇见莫奈的调色盘——API工作流重构企业服务美学
前端·数据库
雷神乐乐5 小时前
Oracle正则表达式学习
数据库·sql·oracle·正则表达式
江沉晚呤时5 小时前
SQL Server 事务详解:概念、特性、隔离级别与实践
java·数据库·oracle·c#·.netcore
斯特凡今天也很帅6 小时前
clickhouse如何查看操作记录,从日志来查看写入是否成功
数据库·clickhouse
菜菜小蒙6 小时前
【MySQL】MVCC与Read View
数据库·mysql
不辉放弃7 小时前
HiveSQL语法全解析与实战指南
数据库·hive·大数据开发
Demisse7 小时前
[MongoDB] 认识MongoDB以及在Windows和Linux上安装MongoDB
linux·windows·mongodb
Elastic 中国社区官方博客7 小时前
Elastic 和 AWS 合作将 GenAI 引入 DevOps、安全和搜索领域
大数据·数据库·elasticsearch·搜索引擎·云计算·全文检索·aws
20242817李臻8 小时前
李臻20242817_安全文件传输系统项目报告_第14周
数据库·安全
MyikJ8 小时前
Java求职面试:从Spring到微服务的技术挑战
java·数据库·spring boot·spring cloud·微服务·orm·面试技巧