章节:第三章 创建、更新及删除文档
时间:2013-07-22
内容:
-
插入并保存文档
-
使用insert方法:如
1db.foo.insert({
"bar"
:
"baz"
})
-
批量插入
-
批量插入能传递一个由文档构成的数组给数据库,且一次批量插入只是单个的TCP请求,无需处理大量的消息头,避免了许多零碎的请求所带来的开销
-
只有插入多个文档到一个集合时,才能提高效率,而不能用批量插入一次对多个集合执行操作
-
要是只导入原始数据,可以使用命令行工具,如mongoimport,而不是批量插入
-
-
插入:原理和作用
-
当执行插入时,使用的驱动程序会将数据转换成BSON形式,然后将其送入数据库
-
数据库解析BSON,检验是否包含“_id”键并且文档不超过4MB
-
MongoDB在插入时不做别的数据校验,也不执行代码,所以也就没有注入式攻击的可能,但副作用是允许插入无效的数据
-
-
-
删除文档
-
删除特定文档,如
1db.mailing.
list
.remove({
"opt-out"
: true})
-
删除集合中的所有文档,如db.users.remove(),但不会删除集合本身,原有的索引也会保留
-
删除速度:通常删除文档会很快,但若要清楚整个集合的文档,则直接删除集合(然后重建索引)会更快,如db.drop_collection("bar")即是删除集合bar
-
-
更新文档
-
更新文档的方法update有2个参数,一个是查询文档,用来找出要更新的文档,另一个是修改器文档,描述对找到的文档做哪些更改
-
更新操作是原子的,若是两个更新同时发生,先到达服务器的先执行,接着执行另外一个
-
文档替换:
-
更新最简单地情形就是完全用一个新文档替代匹配的文档,这适用于模式结构发生了较大变化时,如
1db.users.update({
"name"
:
"joe"
} , joe)
-
常见错误就是查询条件匹配了多个文档,然后更新的时候由于第二次参数的存在就产生重复的“_id”值,数据库会报错,不做任何修改
-
-
使用修改器
-
通常文档只会有一部分要更新,利用原子的更新修改器,可以使得这种部分更新极为高效
-
更新修改器是种特殊的键,用来指定复杂的更新操作,比如调整、增加或者删除键,还可以操作数组或者内嵌文档
-
使用修改器时,"_id"的值不能改变(而整个文档替换时是可以改变的);其他键值,包括其他唯一索引的键,都是可以更改的
-
"$set"修改器:“$set”用来指定一个键的值,若键不存在,则创建它,如
1db.users.update({
"name"
:
"joe"
},...{
"$set"
:{
"favorite book"
:
"green eggs and ham"
}})
也可以用“$unset”将键完全删除,如
1db.users.update({
"name"
:
"joe"
},{
"$unset"
:{
"favorite book"
:
1
}})
-
"$inc"修改器:“$inc”修改器用来增加已有键的值,或者在键不存在时创建一个键,如
1db.games.update({
"game"
:
"pinball"
,
"user"
:
"joe"
},...{
"$inc"
:{
"score"
:
50
}})
“$inc”只能用于整数、长整数或双精度浮点数,用在其它类型的数据上会导致操作失败;另外“$inc”键的值必须为数字,不能使用字符串、数组或其他非数字的值
-
数组修改器:用在值为数组的键上,如果指定的键已存在,"$push"会向已有的数组末尾加入一个元素,要是没有就会创建一个新的数组,如
1db.blog.posts.update({
"title"
:
"A blog post"
},{$push:{
"comments"
:...{
"name"
:
"joe"
,
"email"
:
"joe@example.com"
,
"content"
:
"nice post."
}}})
如果一个值不在数组中再将其加进去,可以用“$ne”或“$addToSet”实现,如
123db.papers.update({
"authors cited"
:{
"$ne"
:
"Richie"
}},...{
"$push"
:{
"authors cited"
:
"Richie"
}})
或
db.users.update({
"_id"
:ObjectId(
"4b2d75476cc6113d5ee930164"
)},...{
"$addToSet"
:{
"emails"
:
"joe@gmail.com"
}})
将“$addToSet”和“$each”组合起来,可以添加多个不同的值,而用“$ne”和“$push”组合就不能实现,如
1db.users.update({
"_id"
:ObjectId(
"4b2d75476cc6113d5ee930164"
},{
"$addToSet"
:...{
"emails"
:{
"$each"
:[
"joe@php.net"
,
"joe@example.com"
,
"joe@python.org"
]}}})
若是把数组看成队列或栈,可以用“$pop”,此修改器可以从数组的任何一端删除元素,{$pop:{key:1}}从数组末尾删除一个元素,{$pop:{key:-1}}则从头部删除
有时需要基于特定条件来删除元素,而不仅仅是依据位置,“$pull”可以做到,“$pull”会将所有匹配到的部分删除,如
1db.lists.update({},{
"$pull"
:{
"todo"
:
"laundry"
}})
-
数组的定位修改器:若是数组有多个值,而我们只想对其中的一部分进行操作,有2种方法可以实现:通过位置或者定位操作符(“$”),如
12345#下标为0表示数组的第一个元素
db.blog.update({
"post"
:post_id},...{
"$inc"
:{
"comments.0.votes"
:
1
}})
#在大多数情况下,不预先查询文档就不能知道要修改数组的下标,故MongoDB提供了定位操作符“$”,用来定位查询文档已经匹配的元素,并进行更新
db.blog.update({
"comments.author"
:
"John"
},...{
"$set"
:{
"comments.$.author"
:
"Jim"
}})
#定位符只更新第一个匹配到的元素
-
修改器速度:$inc能就地修改,且不需要改变文档的大小,故运行非常快,而数据修改器可能更改了文档的大小,就会慢一些;MongoDB预留了些补白给文档,来适应大小变化,但若超出了原来的空间,则会分配新的空间,就会减慢速度,同时随着数组变长,MongoDB需要更长的时间来遍历整个数组,对每个数组的修改也会慢下来。
-
-
upsert:是一种特殊的更新,若是没有文档符合更新条件,就会以这个条件和更新文档为基础创建一个新的文档,若匹配到了文档,则正常更新;update操作的第3个参数表示是否开启upsert,如
1db.analytics.update({
"url"
:
"/blog"
},{
"$inc"
:{
"count"
:
3
}},true)
save Shell帮助程序:save是一个shell函数,可以在文档不存在时插入,存在时更新,只有一个参数:文档;若这个文档含有“_id”键,save会调用upsert,否则调用插入,如
123var x
=
db.foo.findOne()
x.num
=
42
db.foo.save(x)
-
更新多个文档:默认情况下,更新只能对符合匹配条件的第一个文档执行操作,若使所有匹配到的文档都得到更新,可以设置update的第4个参数为true,如
1db.users.update({birthday:
"10/13/1978"
},...{$
set
:{gift:
"Happy Birthday!"
}},false,true)
要知道多少文档被更新了,可以运行getLastError命令,键“n”的值就是要的数字,如
12db.count.update({x:
1
},{$inc:{x:
1
}},false,true)
db.runCommand({getLastError:
1
})
-
返回已更新的文档:用getLastError仅能获得有限的信息,并不能返回已更新的文档,可通过findAndModify命令实现,其调用方式和普通的更新略有不同,有点慢,因为它要等待数据库的响应,如
1234567891011121314151617ps
=
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命令中每个键的对应值:
findAndModify:字符串,集合名
query:查询文档,用来检索文档的条件
sort:排序结果的条件
update:修改器文档,对所找到的文档执行的更新
remove:布尔类型,表示是否删除文档
new:布尔类型,表示返回的是更新前的文档还是更新后的文档,默认是更新前的文档
#findAndModify命令的限制:
#"update"和"remove"必须有一个,也只能有一个;
#若匹配不到文档,则返回错误
#一次只能处理一个文档,也不能执行upsert操作,只能更新已有文档
-
瞬间完成:本章讨论的3个操作(插入、删除、更新)都是瞬间完成的,因为它们都不需要等待数据库响应,只会受客户端发送的速度和网络速度的制约;这对于某些应用(如日志记录,分析数据等)是可以接受的,但对于某些应用(如付费系统)就不适用了
-
安全操作
-
若要完成电子商务系统,则需要一个“安全”版本,保证执行时检查到了错误还可以重来;
-
安全的版本在执行完了操作后立即运行getLastError命令,来检查是否执行成功,驱动程序会等待数据库响应,然后适当地处理错误,一般会抛出一个可被捕获的异常
-
“安全”的代价就是性能,即便忽略客户端处理异常的开销,等待数据库响应本身的时间比只发送消息的时间多一个数量级,所以,应用程序需要权衡数据的重要性(以及丢失后的后果)及速度需求
-
-
捕获“常规”错误
-
安全操作也是一种调试数据库“奇怪”行为的好方法,这样可以避免很多常见的数据库使用错误
-
最常见的就是键重复的错误,即插入一个“_id”值已被占用的文档
-
请求和连接
-
数据库会为每一个MongoDB数据库连接创建一个队列,存放这个连接的请求
-
当客户端发送一个请求,会被放到队列的末尾,只有队列中的请求都执行完毕,后续的请求才会执行
-
所以从单个连接就可以了解整个数据库,并且它总是能读到自己写的东西
-
每个连接都有独立的队列,要是打开2个shell,就有2个数据库连接,在一个shell中执行插入,之后在另一个shell中执行查询不一定能得到插入的文档
-
使用Ruby、Python和Java驱动程序时要特别注意这种行为,因为这几种语言的驱动程序都使用了连接池
-