谈谈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和内存。因此插入这样的数据时,建议预先删除集合上的索引,插入完成后重新构建索引。或向一个没有索引的集合插入数据。

相关推荐
科技小花1 小时前
数据治理平台架构演进观察:AI原生设计如何重构企业数据管理范式
数据库·重构·架构·数据治理·ai-native·ai原生
一江寒逸1 小时前
零基础从入门到精通MySQL(中篇):进阶篇——吃透多表查询、事务核心与高级特性,搞定复杂业务SQL
数据库·sql·mysql
D4c-lovetrain1 小时前
linux个人心得22 (mysql)
数据库·mysql
阿里小阿希2 小时前
CentOS7 PostgreSQL 9.2 升级到 15 完整教程
数据库·postgresql
荒川之神2 小时前
Oracle 数据仓库雪花模型设计(完整实战方案)
数据库·数据仓库·oracle
做个文艺程序员2 小时前
MySQL安全加固十大硬核操作
数据库·mysql·安全
不吃香菜学java2 小时前
Redis简单应用
数据库·spring boot·tomcat·maven
一个天蝎座 白勺 程序猿2 小时前
Apache IoTDB(15):IoTDB查询写回(INTO子句)深度解析——从语法到实战的ETL全链路指南
数据库·apache·etl·iotdb
不知名的老吴2 小时前
Redis的延迟瓶颈:TCP栈开销无法避免
数据库·redis·缓存
YOU OU2 小时前
三大范式和E-R图
数据库