MongoDB学习笔记二:创建、更新及删除文档

时间:2021-03-08 00:30:12

插入并保存文档
对目标集使用insert方法插入一个文档:
> db.foo.insert({"bar" : "baz"})
这个操作会给文档增加一个"_id"键(要是原来没有的话),然后将其保存到MongoDB中。
批量插入
如果要插入多个文档,使用批量插入会快一些。批量插入传递一个由文档构成的数组给数据库。
如果只是导入数据(例如,从数据feed或者MySQL中导入),可以使用命令行工具,如mongoimport,而不是使用批量插入。

删除文档
> db.users.remove()
上述操作会伸出users集合中所有的文档。但不会删除集合本身,原有的索引也会保留。
remove函数可以接受一个查询文档作为可选参数。给定这个参数以后,只有符合条件的文档才被删除。列入,假设要删除mailing.list集合中所有"out-put"为true的人:
> db.mailing.list.remove({"opt-out" : true})
删除数据是永久性地,不能撤销,也不能恢复。

删除速度
删除文档通常会很快,但是要清楚整个集合,直接删除(然后重建索引)hi更快。
例如,在Python中,使用如下方法插入一百万个虚拟元素:
for i in range(1000000):
collection.insert({"foo": "bar", "baz": i, "z": 10 -i })
现在把刚插入的文档都删除,并记录花费的时间。首先来看一个简单的删除(remove):

import time
from pymongo import Collection db = Collection().foo
collection = db.bar start = time.time() collection.remove()
collection.find_one() total = time.time() - start
print "%d seconds" % total

这段脚本输出"46.08 seconds"
如果用db.drop_collection("bar")来代替remove和find_one,只花了.01秒,速度提升相当明显。

更新文档
Ⅰ使用update方法修改存入数据库以后的文档。update有两个参数,一个是查询文档,用来找出需要的文档,另一个是修改器(modifier)文档,描述对修改的文档做哪些修改。
例:
> joe = db.people.findOne({"name" : "joe", "age" : 20})
> joe.age++;
> db.people.update({"name" : "joe"}, joe)
Ⅱ使用修改器
通常文档只会有一部分要更新。利用原子的更新修改器,可以使得这种部分更新极为搞笑。更新修改器是种特殊的键,用来指定复杂的更新操作,比如调整、增加或者删除键,还可能是操作数组或者内嵌文档。
假设要在一个集合中防止网站的分析数据,每当有人访问页面的时候,就要增加计数器。可以使用更新修改器原子性地完成这个增加。每个URL对应的访问次数都以如下的方式存储在文档中:
{
"_id" : ObjectId("4b253b067525f35f94b60a31"),
"url" : "www.example.com",
"pageviews" : 52
}
每次有人访问,就通过URL找到该页面,并用"$inc"修改器增加"pageviews"的值。
> db.analytics.update({"url" : "www.example.com"}, {"$inc" : {"pageviews" : 1}})
接着,执行一个find操作,会发现"pageviews"的值增加了1.
> db.analytics.find()
{
"_id" : ObjectId("4b253b067525f35f94b60a31"),
"url" : "www.example.com",
"pageviews" : 53
}
①"$set"修改器
"$set"用来指定一个键的值。如果这个键不存在,则创建它。
例:添加喜欢的书到用户信息:
> db.users.update({"_id" : ObjectId("4b253b067525f35f94b60a31")}, {"$set" : {"favorite book" : "war and peace"}})
如果用户觉得喜欢的另一本书:
> db.users.update({"name" : "joe"}, {"$set" : {"favorite book" : "green eggs and ham"}})
用"$set"可以修改键的数据类型。例如,如果用户喜欢的是一堆书:
> db.users.update({"name" : "joe"} : {"$set" : {"favorite book" : ["cat's cradle", "fundation trilogy", "ender's game"]}})
如果用户发现自己不爱读书,可以用"$unset"将键完全删除:
> db.users.update({"name" : "joe"}, {"$unset" : {"favorite book" : 1}})
②增加和减少
> db.game.update({"game" :"pinball", "user" : "joe"}, {"$inc" : {"score" : 50}})
③数组修改器
如果指定的键不存在,"$push"会向已有的数组末尾加入一个元素,要是没有就会创建一个新的数组。
例如,假设要存储博客文章,要添加一个包含一个数组的"comments"(评论)键。可以向还不存在的"comments"数组push一个评论,这个数组会被自动创建,并加入评论:
> db.blog.posts.update({"title" : "A blog post"}, {$push : {"comments" : {"name" : "joe", "email" : "joe@example.com", "content" : "nice post."}}})
要是还想添加一条评论,可以接着使用"$push"。
经常会有这种情况,如果一个值不在数组里面就把它加进去。可以在查询文档中用"$ne"来实现。例如,要是坐着不再引文列表中就添加进去,可以这么做:
> db.papers.update({"authors cited" : {"$ne" : "Richie"}}, {$push : {"authors cited" : "Richie"}})
也可以用"$addToSet"完成同样的事情,并且"$addToSet"可以避免重复。
> db.users.update({"_id" : ObjectId("4b2d75476cc613d5ee930164")}, {"$addToSet : {"emails" : "joe@gmail.com"}})
将"$addToSet"和"$each"组合起来,可以添加多个不同的值,而用"$ne"和"$push"组合就不能实现。例如:想一次添加多个邮件地址,就可以使用这些修改器:
> db.users.update({"_id" : ObjectId("4b2d75476cc613d5ee930164")},
{"$addToSet" :
"emails" : {"$each" : ["joe@php.net", "joe@example.com", "joe@python.org"]}})
"$pop"修改器可以从数组任何一端删除元素。{$pop : {key : 1}}从数组末尾删除一个元素,{$pop : {key : -1}}则从头部删除。
"$pull"可以基于特定条件来删除元素,而不仅仅是一句位置。例如,有一个待完成事项列表,顺序有些问题:
> db.list.insert({"todo" : ["dishes", "laundry", "dry cleaning"]})
要是想把洗衣服(alundry)放到第一位,可以从列表中先删掉:
> db.list.update({}, {"$pull" : {"todo" : "laundry"}})
"$pull"会将所有匹配的部分删掉。对数组[1, 1, 2, 1]执行pull 1,得到的结果就是只有一个元素的数组[2]。
④数组的定位修改器
若是数组有多个值,而我们只想对其中的一部分进行操作,这就需要一些技巧。有两种方法操作数组中的值:通过位置或者定位操作符("$")。
数组都是以0开头的,可以将下表直接作为键来选择元素。例如,这里有一个文档,其中包含由内嵌文档组成的数组,比如包含评论的博客文章。
> db.blog.posts.findOne()
{
"_id" : ObjectId("4b329a216cc613d5ee930192")
"content" : "..."
"comments" : [
{
"comment" : "good post"
"author" : "John"
"votes" : 0
},
{
"comment" : "i thought it was too short"
"author" : "Claire"
"votes" : 3
}
{
"comment" : "free watches"
"author" : "Alice"
"votes" : -1
}
]
}
如果想增加第一个评论的投票数量,可以这么做:
> db.blog.update({"post" : post_id}, {"$inc" : {"comments.0.votes" : 1}})
但是很多情况下,不预先查询文档就不能知道要修改数组的下标。为了克服这个困难,MongoDB提供了定位操作符"$",用来定位查询文档已经匹配的元素,并进行更新。例如,要是用户John把名字改成Jim,就可以用定位符替换评论中的名字:
> db.blog.update({"comments.author" : "John"}, {"$set" : {"comments.$.author" : "Jim"}})
定位符只更新第一个匹配的元素。
⑤修改器速度
如果修改操作涉及空间分配会减慢修改操作的速度,同事苏浙数组变长,MongoDB需要更长的时间来遍历整个数组,对每个数组的修改也会慢下来。
下面的Python程序插入一个键,并增加其值100000次(不涉及空间分配):

from pymongo import Connection

import time

db = Connection().performance_test
db.drop_collection("updates")
collection = db.upddates collection.insert({"x" : 1}) # make sure the insert is complete before we start timing collection.find_one() start = time.time() for i in range(100000):
collection.update({} : {"$inc" : {"x" : 1}}) # make sure the updates are complete before we stop timing
collection.find_one() print time.time() - start

一共运行了7.33秒。美妙有13000多次更新。
如果是push 100000次的话:

for i in range(100000):
collection.update({}, {'$push' : {'x' : 1}})

程序花了67.58秒,每秒更新不到1500次。

『upsert』
upsert是一种特殊的更新。要是没有文档符合更新条件,就会以这个条件和更新文档为基础创建一个新的文档。如果找到了匹配的文档,则正常更新。upsert非常方便,不必预置集合,同一套代码可以即创建又更新文档。
回顾之前记录网站页面访问次数的例子。要是没有upsert,就得试着查询URL,没有找到就得新建一个文档,找到的话就增加访问次数。要是把这个鞋城JavaScript程序(而不是用mongo scriptname.js来运行的一系列shell命令),会是如下这样的:
// check if we have an entry for this page
blog = db.analytics.findOne({url : "/blog"})

// if we do, add one to the number of views and save
if (blog) {
blog.pageviews++;
db.analytics.save(blog);
}
// otherwise, create a new document for this page
else {
db.analytics.save({url : "/blog", pageviews : 1})
}
这就是说如果有人访问页面,我们得去数据库打个来回,然后选择更新或者插入。要是多个进程同时运行这段代码,还得考虑对于给定URL不能查如文档的限制。
钥匙使用upsert,即可以避免竞态问题,又可以缩减代码量(update的第3个参数表示这是一个upsert):
> db.analystics.update({"url" : "/blog"}, {"$inc" : {"visits" : 1}}, true)
这行代码和之前的代码作用完全一样,但它更高效,并且是原子性的。
创建新文档会将条件文档作为基础,然后将修改器文档应用于其上。例如,要是执行一个匹配键并增加对应键值的upsert操作,会在匹配的基础上进行增加:
> db.math.remove()
> db.math.update({"count" : 25}, {"$inc" : {"count" : 3}}, true)
> db.math.findOne()
{
"_id" : ObjectId("4b3295f26cc613d5ee93018f"),
"count" : 28
}
先是remove清空了集合,里面就没有文档了。upsert创建了一个键"count"的值为25的文档,随后将这个值加3,最后得到"count"为28的文档。要是不开启upsert选项,{"count" : 25}不会匹配到任何文档,也就没有任何更改。
要是将这个upsert(条件为{count : 25})再次运行,还会创建一个新文档。这是因为没有文档匹配条件(唯一的文档的"count"的值是28)。
save Shell帮助程序
save是一个shell函数,可以在文档不存在时插入,存在时更新。他只有一个参数:文档。要是这个文档含有"_id"键,save会调用upsert。否则,会调用插入。程序员可以非常方便地使用这个函数在shell中快速修改文档。
> var x = db.foo.findOne()
> x.num = 42
> db.foo.save(x)
要是不用save,最后一行可以像下面这样写,但很啰嗦:
> db.foo.update({"_id" : x._id}, x)
更新多个文档
默认情况下,更新只能对符合条件的第一个文档执行操作。要是有多个文档符合条件,其余的文档就没有变化。要使所有匹配到的文档都得到更新,可以设置update的第4个参数为true。
多文档更新对模式迁移非常有用,还可以在对特定用户发布新功能的时候使用。例如,假设要给所有在特定日期过生日的用户一份礼物,就可以使用多文档更新,将"gift"增加到他们的账号。
> db.users.update({birthday : "10/13/1978"}, {$set : {gift : "Happy Birthday!"}}, false, true)
这样就给生日为1978年10月113日的所有用户文档添加了"gift"键。
想要知道多文档更新到底更新了多少文档,可以运行getLastError命令。键"n"的值就是要的数字。
> db.count.update({x : 1}, {$inc : {x : 1}}, false, true)
> db.runCommand({getLastError : 1})
{
"err" : null,
"updatedExisting" : true,
"n" : 5,
"ok" : true
}
这里"n"为5,说明5个文档被更新了。"updatedExisting"为true,说明是对已有的文档进行更新。

返回已更新的文档:findAndModify命令。
例:假设我们有一个集合,其中包括以一定顺序运行的进程。其中每个进程都被表示为具有如下形式的文档:
{
"_id" : ObjectId(),
"status" : stat,
"priority" : N
}
"status"是一个字符串,可以是"READY"、"RUNNING"或"DONE"。要找到状态为"READY"的具有最高优先级的任务,运行进程函数,然后更新其状态为"DONE"。将已经就绪的进程按照优先级排序,然后将优先级最高的进程的状态更新为"RUNNING"。完成了以后,就把状态改为"DONE"。
使用"findAndModify"的程序:
> ps = db.runCommand({"findAndModify" : "processes",
"query" : {"status" : "READY"},
"sort" : {"priority" : -1},
"update" : {"$set" : {"status" : "RUNNING"}}}).value
> do_something(ps)
> db.process.update({"_id" : ps._id}, {"$set" : {"status" : "DONE"}})
findAndModify既有"update"键也有"remove"键。"remove"键表示将匹配到的文档从集合里面删除。例如,现在不要更新状态了,而是直接删掉,就可以像下面这样:
> ps = db.runCommand({"findAndModify" : "processes",
"query" : {"status" : "READY"},
"sort" : {"priority" : -1},
"remove" : true}).value
> do_something(ps)
findAndModify命令中每个键对应的值如下所示。
· findAndModify
字符串,集合名
· query
查询文档,用来检索文档的条件
· sort
排序结果的条件
· update
修改器文档,对所找到的文档执行的更新。
· remove
布尔类型,表示是否删除文档。
· new
布尔类型,表示返回的是更新前的还是更新后的文档。默认是更新前的文档。