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个操作(插入、删除、更新)都是瞬间完成的,不需要等待数据库响应。
当然这并不是异步操作,可以想象成客户端动作发出后就不管了,客户端永远不会受到服务端类似于“好的,知道了”或者“有问题,能重新传送一遍吗”这样的响应。
这种特点的优点很明显,速度快。但是对数据的安全性保障级别不足,如服务器崩溃了,网线被老鼠咬断了,数据中心被洪水淹没了。
在没有服务器的情况下,客户端还是会发送写操作到服务器的,完全不用理会服务器是否正常工作。
对于有些应用这是可以接受的,如果涉及到金融系统,则需要慎之又慎。