DBA MongoDB 文档操作

插入文档

基础方法

​ 下面是向集合插入文档的基础命令:

命令 描述
db.集合名.insert(doc) 向集合中插入一个文档,若主键存在,则抛出异常

​ 插入的基础方法为insert(),支持最大一次性插入4MB(新版好像是16MB了)的文档,该操作会给文档增加一个名为_id的键(未手动指定的情况下),其类型为objectId对象,关于objectId对象在数据类型中已有介绍,如果你感兴趣,可以点我跳转

​ 示例演示如下:

> db.collection.insert({"name" : "jack"})
WriteResult({ "nInserted" : 1 })

​ 为了方便查看结果,再介绍一个查看命令,如下所示:

> db.collection.find().pretty()
{ "_id" : ObjectId("60486be289d49aa11b240ad8"), "name" : "jack" }

扩展方法

​ 在MongoDB3.2之后,对插入文档提供了两个新方法:

命令 描述
db.集合名.insertOne(doc) 向集合中插入一个新文档,返回_id
db.集合名.insertMany([doc, doc]) 向集合中插入一个或多个文档,返回_ids

​ 参数说明如下:

  • document:要写入的文档
  • writeConcern:写入策略,默认为1,即立即写入磁盘,如果为0则是不要求
  • ordered:仅针对批量写入,是否按照指定顺序写入,默认为true

​ 示例演示如下:

> db.collection.insertOne({"name" : "Tom"})
{
	"acknowledged" : true,
	"insertedId" : ObjectId("60486ea089d49aa11b240ad9")
}
> db.collection.insertMany([{"name" : "Ken"}, {"name" : "Mary"}])
{
	"acknowledged" : true,
	"insertedIds" : [
		ObjectId("60486f0789d49aa11b240ada"),
		ObjectId("60486f0789d49aa11b240adb")
	]
}

批量好处

​ 如果要插入多个文档,使用批量插入会更快一些。

​ 这是由于批量插入只传递一个由文档构成的数组给数据库,一次批量插入仅发送一次TCP请求。

​ 这可以有效避免许多零碎的请求所带来的开销,无需处理大量的消息头,能够减少插入时间。

​ 当使用单次插入进行循环时,每一次的TCP请求都会有一个TCP头部信息,而批量插入由于只发送一次TCP请求,所以服务端解析TCP信息的次数与时间就会大幅度的减少。

​ 当前版本(书中)的MongoDB最大消息长度是16MB,所以使用批量插入还是有限制的。

作用原理

​ 当执行插入操作时,使用的驱动程序会将数据转换为BSON形式,然后将其传送给数据库。

​ 数据库解析BSON,检验是否有_id键且文档大小不超过4MB(新版扩容了)。

​ 当解析完成后,不会做其他额外的操作而是将文档直接存储在数据库中。

​ 这样的操作有好有坏,最明显的副作用就是允许插入无效的数据,而从好处看,它能让数据更加安全,远离注入式攻击。

​ 如果在使用主流语言的驱动传输数据进行解析前,可对被传送数据做一些有效的检查,如文档是否超长,是否包含非utf8字符,或者使用了位置类型,要是对使用的驱动不熟悉,则可以在启动数据库服务器时使用--objectcheck选项,这样服务器就会在文档插入之前检查结构的有效性(当然要牺牲些许性能)。

​ MongoDB在做插入数据时,并不会执行任何代码,所以对于这块没有注入攻击的可能性,传统的注入式攻击对MongoDB来说是无效的。

​ 附:想查看一个文档的大小,可以在shell中使用以下函数(以字节为单位):

> Object.bsonsize({"name" : "Jack"})
20

删除文档

基础方法

​ 下面是对集合删除文档的基础命令:

命令 描述
db.集合名.remove(query) 删除集合中符合条件的文档

​ 参数说明如下:

  • query:删除文档的条件
  • justOne:(可选)如果设置为true或者1,则只删除一个文档,否则删除所有匹配到的文档,参数默认为false
  • writeConcern:(可选)抛出异常的级别

​ 其实这个命令在于早期的MongoDB中是非常常用的,但是现在,官方更推荐使用下面的扩展方法。

​ 故在此不对该方法进行演示。

​ 不论是这个基础方法还是下面所介绍的扩展方法,对数据的删除操作都是永久性的,即不能撤销,也不能恢复。

扩展方法

​ 在新版MongoDB中,更推荐使用下面两个扩展方法来进行删除文档的操作:

命令 描述
db.集合名.deleteOne(query) 删除集合中的第一个符合条件的文档
db.集合名.deleteMany(query) 删除集合中所有的符合条件的文档
  • 对于deleteOne()方法来说,相当于remove()方法中的justOne参数设置为true。

  • 而对于deleteMany()方法来说,则与上面的deleteOne()刚好相反。

​ 示例演示如下,首先,我们先在collection中插入3个一样的文档(实际上objectId不一样):

> db.collection.drop()
true
> db.collection.insertMany([{"name" : "Jack"}, {"name" : "Jack"}, {"name" : "Jack"}])
{
	"acknowledged" : true,
	"insertedIds" : [
		ObjectId("60487a1089d49aa11b240adc"),
		ObjectId("60487a1089d49aa11b240add"),
		ObjectId("60487a1089d49aa11b240ade")
	]
}

​ 使用deleteOne()方法来删除主键后缀值为abc的文档:

> db.collection.deleteOne({"name" : "Jack"})
{ "acknowledged" : true, "deletedCount" : 1 }
> db.collection.find().pretty()
{ "_id" : ObjectId("60487a1089d49aa11b240add"), "name" : "Jack" }
{ "_id" : ObjectId("60487a1089d49aa11b240ade"), "name" : "Jack" }

​ 现在,使用deleteMany()方法来删除剩余的两个文档:

> db.collection.deleteMany({"name" : "Jack"})
{ "acknowledged" : true, "deletedCount" : 2 }
> db.collection.find().pretty()

删除速度

​ 如果只删除一个集合下的所有的文档,可能花费的时间比较长。

​ 而直接删除一个集合,所花费的时间就会非常的短。

​ 同时,仅删除文档,该集合可能还存在一些索引信息,而直接删除一个集合,所有关于该集合以及其下文档的信息都会被删除,包括索引。

更新文档

基础方法

​ 下面是对集合更新文档的基础命令:

命令 描述
db.集合名.update(query, update) 对一个集合中筛选的第一个文档做替换(加了修改器$set是更新)
db.集合名.save(doc) 集合中有该文档则更新,没有则创建,需要使用主键进行筛选

​ 比较常用的还是update(),关于它的参数说明如下:

  • query:更新的查询条件
  • update:选定替换或更新的对象和添加的修改器
  • upsert:(可选)如果为true,则与save()方法效果相同,默认为false
  • multi:(可选)如果为true,则替换所有符合条件的文档,默认为false
  • writeConcern:(可选)抛出异常的级别

​ 下面对update()方法做示例演示,插入3个一样的文档,使用update()方法进行文档替换:

这里仅代表示例演示,实际使用时update()方法的query最好是_id键

> db.collection.drop()
true
> db.collection.insertMany([{"name" : "Jack"}, {"name" : "Jack"}, {"name" : "Jack"}])
{
	"acknowledged" : true,
	"insertedIds" : [
		ObjectId("604887e70b6bcfe9e15b9856"),
		ObjectId("604887e70b6bcfe9e15b9857"),
		ObjectId("604887e70b6bcfe9e15b9858")
	]
}
> db.collection.update({"name" : "Jack"}, {"age" : "18"})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.collection.find().pretty()
{ "_id" : ObjectId("604887e70b6bcfe9e15b9856"), "age" : "18" }
{ "_id" : ObjectId("604887e70b6bcfe9e15b9857"), "name" : "Jack" }
{ "_id" : ObjectId("604887e70b6bcfe9e15b9858"), "name" : "Jack" }

扩展方法

​ 在新版MongoDB中,更推荐使用下面两个扩展方法来进行更新文档的操作:

命令 描述
db.集合名.updateOne(query, update) 对一个集合中筛选的第一个旧文档做更新,需要修改器$set
db.集合名.updateMany(query, update) 对一个结合中筛选的所有符合条件文档做更新,需要添加修改器$set
  • 对于updateMany()方法来说,相当于update()方法中的multi参数设置为true。

  • 而对于updateOne()方法来说,则与上面的updateMany()刚好相反。

​ 示例演示如下,首先,我们先在collection中插入3个一样的文档(实际上objectId不一样):

> db.collection.drop()
true
> db.collection.insertMany([{"name" : "Jack"}, {"name" : "Jack"}, {"name" : "Jack"}])
{
	"acknowledged" : true,
	"insertedIds" : [
		ObjectId("604889630b6bcfe9e15b9859"),
		ObjectId("604889630b6bcfe9e15b985a"),
		ObjectId("604889630b6bcfe9e15b985b")
	]
}

​ 使用updateOne()方法更新,仅更新第一条主键值后缀为859的文档:

> db.collection.updateOne({"name" : "Jack"}, {$set:{"name" : "NewJack"}})
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
> db.collection.find().pretty()
{ "_id" : ObjectId("604889630b6bcfe9e15b9859"), "name" : "NewJack" }
{ "_id" : ObjectId("604889630b6bcfe9e15b985a"), "name" : "Jack" }
{ "_id" : ObjectId("604889630b6bcfe9e15b985b"), "name" : "Jack" }

​ 现在,使用updateMany()方法来更新剩余的两个文档:

> db.collection.updateMany({"name" : "Jack"}, {$set:{"name" : "NewJack"}})
{ "acknowledged" : true, "matchedCount" : 2, "modifiedCount" : 2 }
> db.collection.find().pretty()
{ "_id" : ObjectId("604889630b6bcfe9e15b9859"), "name" : "NewJack" }
{ "_id" : ObjectId("604889630b6bcfe9e15b985a"), "name" : "NewJack" }
{ "_id" : ObjectId("604889630b6bcfe9e15b985b"), "name" : "NewJack" }

​ 有关于$修改器,会在下面介绍到。这里使用的$set代表仅更新一条记录,而非替换整个文档。

​ update()方法可以不加修改器,这意味着替换整个文档

​ 而对于updateOne()和updateMany()来说,它们必须添加修改器,否则会引发语法错误的异常

$set&$unset

​ 使用$set修改器来指定更新某一个键的值,而不是替换全文档,如果这个键不存在,则会进行创建。

​ 使用$unset修改器来指定删除某一个键值对。

​ 这两个修改器的语法格式如下:

修改器 语法格式
$set {$set : { k : v}}
$unset {$unset : { k : 1}}

​ 示例如下,先是使用$set修改器修改器age的值,然后使用$unset删除age这个键值对:

> db.collection.drop()
true
> db.collection.insertOne({"name" : "Jack", "age" : 18})
{
	"acknowledged" : true,
	"insertedId" : ObjectId("60488d600b6bcfe9e15b985c")
}
> db.collection.updateOne({"_id" : ObjectId("60488d600b6bcfe9e15b985c")}, {$set : {"age" : 20}})
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
> db.collection.find().pretty()
{
	"_id" : ObjectId("60488d600b6bcfe9e15b985c"),
	"name" : "Jack",
	"age" : 20
}
> db.collection.updateOne({"_id" : ObjectId("60488d600b6bcfe9e15b985c")}, {$unset : {"age" : 1}})
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
> db.collection.find().pretty()
{ "_id" : ObjectId("60488d600b6bcfe9e15b985c"), "name" : "Jack" }

$inc

​ 使用$inc修改器来对某一键的值进行自加或自减,如果这个键不存在,则会进行创建。

​ 需要注意的是$inc仅能针对整数、长整数、双精度浮点数的value做更新。

​ 修改器的语法格式如下:

修改器 语法格式
$inc {$inc : {k : +int||-int}}

​ 示例如下,先将分数增加10,再减20:

> db.collection.drop()
true
> db.collection.insertOne({"name" : "Jack", "grades": 90})
{
	"acknowledged" : true,
	"insertedId" : ObjectId("60488fab0b6bcfe9e15b985d")
}
> db.collection.updateOne({"_id" :  ObjectId("60488fab0b6bcfe9e15b985d")}, {$inc : {"grades" : +10}})
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
> db.collection.find().pretty()
{
	"_id" : ObjectId("60488fab0b6bcfe9e15b985d"),
	"name" : "Jack",
	"grades" : 100
}
> db.collection.updateOne({"_id" :  ObjectId("60488fab0b6bcfe9e15b985d")}, {$inc : {"grades" : -20}})
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
> db.collection.find().pretty()
{
	"_id" : ObjectId("60488fab0b6bcfe9e15b985d"),
	"name" : "Jack",
	"grades" : 80
}

普通修改器

​ 普通修改器仅针对于普通的value(非内嵌文档,数组),以下是常用的普通修改器的大全:

修改器 语法格式 描述
$set {$set : { k : v}} 指定更新某一个键的值,而不是替换全文档,如果这个键不存在,则会进行创建
$unset {$unset : { k : 1}} 指定删除某一个键值对
$inc {$inc : {k : +int||-int}} 对某一键的值进行自加或自减,如果这个键不存在,则会进行创建
$mul {$mul : {k : int}} 对某一键的值进行乘法操作
$rename {$rename : {okn : nkn}} 对某一键进行重命名操作,okn->old key name,nkn-> new key name
$min {$min : {k : v}} 如果原key中的value大于指定的value则更新
$max {$max : {k : v}} 如果原key中的value小于指定的value则更新
$currentDate {$currentDate : {k : v}} 指定更新某一key的value为当前时间

​ 想要了解更多的普通修改器,请参照官方文档。

​ 示例如下:

# 插入一个文档
> db.item.insert({"_id" : 1, "num" : 10, time : "2020-01-02 12:00:00"})
WriteResult({ "nInserted" : 1 })

# $inc,对num字段自加1
> db.item.update({"_id" : 1}, {$inc : {"num" : +1}})
{ "_id" : 1, "num" : 11, "time" : "2020-01-02 12:00:00" }

# $mul,对num字段乘以2
> db.item.update({"_id" : 1}, {$mul : {"num" : 2}})
{ "_id" : 1, "num" : 22, "time" : "2020-01-02 12:00:00" }

# $set,只更新num字段, 如果不指定$set,则整个文档都会替换为{"num":1}
> db.item.update({"_id" : 1}, {$set : {"num" : 1}})
{ "_id" : 1, "num" : 1, "time" : "2020-01-02 12:00:00" }

# $unset,删除num字段
> db.item.update({"_id" : 1}, {$unset : {"num" : 1}})
{ "_id" : 1, "time" : "2020-01-02 12:00:00" }

# $set,添加num字段
> db.item.update({"_id" : 1}, {$set : {"num" : 1}})
{ "_id" : 1, "time" : "2020-01-02 12:00:00", "num" : 1 }

# $min,更新num字段,如果原字段值大于0则更新为0,否则不操作
> db.item.update({"_id" : 1}, {$min : {"num" : 0}})
{ "_id" : 1, "time" : "2020-01-02 12:00:00", "num" : 0 }

# $max,更新num字段,如果原字段值小于100则更新为100,否则不操作
> db.item.update({"_id" : 1}, {$max : {"num" : 100}})
{ "_id" : 1, "time" : "2020-01-02 12:00:00", "num" : 100 }

# $currentDate,更新time字段为当前时间,时间戳形式,$type为时间戳类型
> db.item.update({"_id" : 1}, {$currentDate : {"time" : {$type : "timestamp"}}});
{ "_id" : 1, "time" : Timestamp(1615335076, 1), "num" : 100 }

数组修改器

​ 数组修改器仅针对于value的值为数组类型,以下是常用的普通修改器的大全:

修改器 语法格式 描述
$addToset {$addToset : {k : v}} 更新的key必须是数组类型,朝数组中添加元素,添加至最后
$push {$push : {k : v}} 若文档中数组不存在,则创建并插入元素,若存在则更新添加元素
$position 更新的key必须是数组类型,配合$push与$each使用,向指定位置的后面插入元素
$each 更新的key必须是数组类型,配合$push或$addToSet使用达到批量插入更新的作用
$pop {$pop:{k : 1||-1}} 更新的key必须是数组类型,从数组移除key的第1个元素(-1),或者最后一个元素(1)
$pull {$pullALL : {k : [v1 , v2]}} 更新的key必须是数组类型,删除数组中指定的所有元素

​ 如果想了解更多命令,请参照官方文档。

​ 示例如下:

# 插入一个数组
> db.array.insert({"_id" : 1, v : [1, 2, 3, 4, 5]});
WriteResult({ "nInsertead" : 1 })

# $addToSet,向v中更新一个元素6
> db.array.updateOne({"_id" : 1}, {$addToSet : {v : 6}});
{ "_id" : 1, "v" : [ 1, 2, 3, 4, 5, 6 ] }

# $push,向v中更新一个元素7(已存在)
> db.array.updateOne({"_id" : 1}, {$push : {v : 7}});
{ "_id" : 1, "v" : [ 1, 2, 3, 4, 5, 6, 7 ] }

# $each与$position,向v中指定位置插入3个元素
> db.array.updateOne({"_id" : 1}, {$push : {v : {$each : [8, 9, 10], $position : 3}}});
{ "_id" : 1, "v" : [ 1, 2, 3, 8, 9, 10, 4, 5, 6, 7 ] }

# $pullAll,删除v中的指定元素
> db.array.updateOne({"_id" : 1}, {$pullAll : {v : [1, 2, 3]}});
{ "_id" : 1, "v" : [ 8, 9, 10, 4, 5, 6, 7 ] }

内嵌文档

.访问

​ 使用.来访问内嵌文档,并对成绩进行修改:

> db.collection.drop()
true
> db.collection.insertOne({"name" : "Jack", "grades" : {"Js" : 80, "Py" : 60}})
{
	"acknowledged" : true,
	"insertedId" : ObjectId("604896e70b6bcfe9e15b985e")
}
> db.collection.updateOne({"_id" : ObjectId("604896e70b6bcfe9e15b985e")}, {$set : {"grades.Js" : 100}})
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
> db.collection.updateOne({"_id" : ObjectId("604896e70b6bcfe9e15b985e")}, {$set : {"grades.Py" : 100}})
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
> db.collection.find().pretty()
{
	"_id" : ObjectId("604896e70b6bcfe9e15b985e"),
	"name" : "Jack",
	"grades" : {
		"Js" : 100,
		"Py" : 100
	}
}

性能相关

​ 本章节所探讨的3个操作(插入、删除、更新)都是瞬间完成的,不需要等待数据库响应。

​ 当然这并不是异步操作,可以想象成客户端动作发出后就不管了,客户端永远不会受到服务端类似于“好的,知道了”或者“有问题,能重新传送一遍吗”这样的响应。

​ 这种特点的优点很明显,速度快。但是对数据的安全性保障级别不足,如服务器崩溃了,网线被老鼠咬断了,数据中心被洪水淹没了。

​ 在没有服务器的情况下,客户端还是会发送写操作到服务器的,完全不用理会服务器是否正常工作。

​ 对于有些应用这是可以接受的,如果涉及到金融系统,则需要慎之又慎。

posted @ 2021-03-12 19:14  云崖君  阅读(56)  评论(0编辑  收藏  举报