MongoDB学习系列14:杂七杂八

1.插入和保存document
如前所述,向collection插入document使用insert方法
>  db.foo.insert({ " bar "  :  " baz " })
如果document里边没有"_id"键,"_id"会被自动创建
批量插入
批量插入是一种更高效的方法,传递给database一个document的数组,可以一次插入多个document。单个插入的时候,向 database传送一个document,前边会附加一个头部,告诉database在某个collection执行一次插入操作。批量插入只产生一个 TCP请求,意味着不用处理很多请求,同时也省掉了处理头部的时间。批量插入只能插入到一个collection里边去。
批量插入只能用于应用程序接口,shell不支持(至少到目前还不支持)。
另外,如果想导入数据(比如说从mysql),不要使用批量插入,使用命令行工具如mongoimport。


2.删除document
>  db.users.remove()
这个命令会删除users里边的所有document。
remove函数可以有一个查询用document做参数,以删除符合条件的document。
>  db.mailing.list.remove({ " opt-out "  :  true })
这个命令删除所有"opt-out"为true的document。
删除文档通常是一个非常快的操作,如果想清除整个collection,还有一种更快的方法,使用drop函数然后重建索引。


3.更新document
udpate方法可以携带两个参数:
查询用document, 用于定位哪些document将会被更新
修饰符document,用于描述如何修改找到的document
更新是原子性操作,先到达服务器的将会被先执行,后到达的会被后执行,所以,后边的会覆盖前边的修改。
document替换
使用一个新的document来替换匹配的,上一篇文章里用的其实就是document替换,如
db.users.update({ " name "  :  " joe " }, joe);
document替换时一个常见的错误是当有多个匹配的document时候可能会导致duplicate key错误。举个例子,
假设我们有好几个名字都叫joe的document,
>  db.people.find()
{ " _id "  : ObjectId( " 4b2b9f67a1f631733d917a7b " ),  " name "  :  " joe " ,  " age "  :  65 },
{ " _id "  : ObjectId( " 4b2b9f67a1f631733d917a7c " ),  " name "  :  " joe " ,  " age "  :  20 },
{ " _id "  : ObjectId( " 4b2b9f67a1f631733d917a7d " ),  " name "  :  " joe " ,  " age "  :  49 },
现在,2号joe(20岁那个)生日到了,我们要给他的年龄加1,
>  joe  =  db.people.findOne({ " name "  :  " joe " ,  " age "  :  20 });
{
" _id "  : ObjectId( " 4b2b9f67a1f631733d917a7c " ),
" name "  :  " joe " ,
" age "  :  20 
}
>  joe.age ++ ;
>  db.people.update({ " name "  :  " joe " }, joe);
E11001 duplicate key on update
Oh,出错了,怎么回事?数据库查找name为joe的document,找到的第一个是65岁那个,然后试图替换这个document,然而
数据库里边已经有一个"_id"为"4b2b9f67a1f631733d917a7c" 的记录了,”_id"是不可重复的,所以就有个这个错误。
所以执行document替换的时候要小心,确认你要替换的是唯一一个符合条件的。
使用修饰符
通常情况下我们只想更新document的一部分,我们可以使用更新修饰符来做到这一点。
假设我们有一个记录网站访问信息的一个collection,里边的document像这个样子
{
" _id "  : ObjectId( " 4b253b067525f35f94b60a31 " ),
" url "  :  " www.example.com " ,
" pageviews "  :  52 
}
pageviews是站点的访问次数,那么我想给它增加1的时候就可以这样子做
>  db.analytics.update({ " url "  :  " www.example.com " },
... { " $inc "  : { " pageviews "  :  1 }})
"$inc"就是个更新修饰符,使用更新修饰符的时候,不能更新"_id"键的值。
下边我们看看常用的更新修饰符
$set
$set修饰符设定指定的key的值,如果key不存在就创建一个
假设我们有下边一个用户档案
>  db.users.findOne()
{
" _id "  : ObjectId( " 4b253b067525f35f94b60a31 " ),
" name "  :  " joe " ,
" age "  :  30 ,
" sex "  :  " male " ,
" location "  :  " Wisconsin " 
}
现在我们想给用户加一个项目,他喜爱的书籍,我们就可以用$set修饰符来做
>  db.users.update({ " _id "  : ObjectId( " 4b253b067525f35f94b60a31 " )},
... { " $set "  : { " favorite book "  :  " war and peace " }})
看看结果
>  db.users.findOne()
{
" _id "  : ObjectId( " 4b253b067525f35f94b60a31 " ),
" name "  :  " joe " ,
" age "  :  30 ,
" sex "  :  " male " ,
" location "  :  " Wisconsin " ,
" favorite book "  :  " war and peace " 
}
如果用户不喜欢这本书了,喜欢另为一本,我们可以再次用$set来修改
>  db.users.update({ " name "  :  " joe " },
... { " $set "  : { " favorite book "  :  " green eggs and ham " }})
$set不仅可以改变值,还可以改变数据类型,如果用户喜欢的不是一本,而是很多书
>  db.users.update({ " name "  :  " joe " },
... { " $set "  : { " favorite book "  :
... [ " cat's cradle " ,  " foundation trilogy " ,  " ender's game " ]}})
如果用户现在又觉得,他其实并不喜欢读书,我们可以用$unset来删除key
>  db.users.update({ " name "  :  " joe " },
... { " $unset "  : { " favorite book "  :  1 }})
$inc
$inc修饰符只能用于数字,增加一个指定的数量

数组修饰符$push
$push向数组尾部追加一个元素,如果数组不存在则创建一个数组
例如,我想给一篇博客文章添加评论,而评论这个key还不存在
>  db.blog.posts.findOne()
{
" _id "  : ObjectId( " 4b2d75476cc613d5ee930164 " ),
" title "  :  " A blog post " ,
" content "  :  " ... " 
}
>  db.blog.posts.update({ " title "  :  " A blog post " }, {$push : { " comments "  :
... { " name "  :  " joe " ,  " email "  :  " joe@example.com " ,  " content "  :  " nice post. " }}})
>  db.blog.posts.findOne()
{
" _id "  : ObjectId( " 4b2d75476cc613d5ee930164 " ),
" title "  :  " A blog post " ,
" content "  :  " ... " ,
" comments "  : [
{
" name "  :  " joe " ,
" email "  :  " joe@example.com " ,
" content "  :  " nice post. " 
}
]
}
如果想添加另外一篇评论,就再次使用$push
>  db.blog.posts.update({ " title "  :  " A blog post " }, {$push : { " comments "  :
... { " name "  :  " bob " ,  " email "  :  " bob@example.com " ,  " content "  :  " good post. " }}})
>  db.blog.posts.findOne()
{
" _id "  : ObjectId( " 4b2d75476cc613d5ee930164 " ),
" title "  :  " A blog post " ,
" content "  :  " ... " ,
" comments "  : [
{
" name "  :  " joe " ,
" email "  :  " joe@example.com " ,
" content "  :  " nice post. " 
},
{
" name "  :  " bob " ,
" email "  :  " bob@example.com " ,
" content "  :  " good post. " 
}
]
}
如果想向数组里追加一个数组里没有的元素,我们可以在查询document里使用$ne(not equal,第四章查询里会有这种逻辑修饰符的说明),如:
>  db.papers.update({ " authors cited "  : { " $ne "  :  " Richie " }},
... {$push : { " authors cited "  :  " Richie " }})
数组修饰符$addToSet可以达到相同的效果,假设我们有个用户document,用户有好几个email
>  db.users.findOne({ " _id "  : ObjectId( " 4b2d75476cc613d5ee930164 " )})
{
" _id "  : ObjectId( " 4b2d75476cc613d5ee930164 " ),
" username "  :  " joe " ,
" emails "  : [
" joe@example.com " ,
" joe@gmail.com " ,
" joe@yahoo.com " 
]
}
我们可以使用$addToSet防止插入重复的值
>  db.users.update({ " _id "  : ObjectId( " 4b2d75476cc613d5ee930164 " )},
... { " $addToSet "  : { " emails "  :  " joe@gmail.com " }})
>  db.users.findOne({ " _id "  : ObjectId( " 4b2d75476cc613d5ee930164 " )})
{
" _id "  : ObjectId( " 4b2d75476cc613d5ee930164 " ),
" username "  :  " joe " ,
" emails "  : [
" joe@example.com " ,
" joe@gmail.com " ,
" joe@yahoo.com " ,
]
}
>  db.users.update({ " _id "  : ObjectId( " 4b2d75476cc613d5ee930164 " )},
... { " $addToSet "  : { " emails "  :  " joe@hotmail.com " }})
>  db.users.findOne({ " _id "  : ObjectId( " 4b2d75476cc613d5ee930164 " )})
{
" _id "  : ObjectId( " 4b2d75476cc613d5ee930164 " ),
" username "  :  " joe " ,
" emails "  : [
" joe@example.com " ,
" joe@gmail.com " ,
" joe@yahoo.com " ,
" joe@hotmail.com " 
]
}
$addToSet还可以和$each联合使用,一次追加多个不重复的值,这个是$ne/$push组合做不到的
>  db.users.update({ " _id "  : ObjectId( " 4b2d75476cc613d5ee930164 " )}, { " $addToSet "  :
... { " emails "  : { " $each "  : [ " joe@php.net " ,  " joe@example.com " ,  " joe@python.org " ]}}})
>  db.users.findOne({ " _id "  : ObjectId( " 4b2d75476cc613d5ee930164 " )})
{
" _id "  : ObjectId( " 4b2d75476cc613d5ee930164 " ),
" username "  :  " joe " ,
" emails "  : [
" joe@example.com " ,
" joe@gmail.com " ,
" joe@yahoo.com " ,
" joe@hotmail.com " 
" joe@php.net " 
" joe@python.org " 
]
}

数组修饰符$pop
$pop修饰符从数组的两端移除一个元素,{$pop : {key : 1}}从数组末端删除一个元素,{$pop :{key : -1}}从数组起始端删除一个元素

数组修饰符$pull
$pull从数组里移除符合条件的元素
比如,我们有一个待做事项列表
>  db.lists.insert({ " todo "  : [ " dishes " ,  " laundry " ,  " dry cleaning " ]})
我们想把laundry删掉
>  db.lists.update({}, { " $pull "  : { " todo "  :  " laundry " }})
这样数组里就剩两项了
>  db.lists.find()
{
" _id "  : ObjectId( " 4b2d75476cc613d5ee930164 " ),
" todo "  : [
" dishes " ,
" dry cleaning " 
]
}
$pull会删除所有的匹配元素

按照位置操作数组的值
有两种方式:一是按照位置,二是使用位置操作符($符号)
我们先看第一种用法,位置是从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 
}
]
}
我想给第一篇评论的投票数加1,我们就可以这样做
>  db.blog.update({ " post "  : post_id},
... { " $inc "  : { " comments.0.votes "  :  1 }})
实际上很多时候我们根本不知道这个索引是多少,我们只知道有这么个匹配的document在,我们可以使用第二种用法,使用位置操作符$,
$就代表了匹配的元素的索引,如果我们想把评论里叫John的那个改成Jim,就可以这样子做
db.blog.update({ " comments.author "  :  " John " },
... { " $set "  : { " comments.$.author "  :  " Jim " }})


4.Upsert
这估计是作者自己造的单词,指如果存在匹配的document就更新,如果不存在匹配就插入。
将update函数的第三个参数设为true即可,如:
db.analytics.update({ " url "  :  " /blog " }, { " $inc "  : { " visits "  :  1 }},  true )
shell的save函数也可以达到同样的目的,如果存在就更新,如果不存在就插入。
save函数使用一个document做参数,如果document有"_id"键就更新,如果没有就插入。
>   var  x  =  db.foo.findOne()
>  x.num  =   42 
42 
>  db.foo.save(x)


5.更新多个document
缺省情况下,update函数只更新匹配的第一条记录,余下的不做改变,要想更新所有的匹配记录,将update函数的第4个参数设为true
>  db.users.update({birthday :  " 10/13/1978 " },
... {$set : {gift :  " Happy Birthday! " }},  false ,  true )


6.返回被更新的document
findAndModify命令的调用比普通的update要慢一些,因为它要等待服务器的响应。
findAndModify命令适合处理队列,或者其他的原子性的get-and-set式的操作。
假设我们有一个处理流程的collection,需要按一定的顺序执行,一个document代表了一个处理流程,如下
{
" _id "  : ObjectId(),
" status "  : state,
" priority "  : N
}
status是个字符串,可能的值是"Ready","Running","Done".我们需要找到Ready状态优先级最高的处理流程,处理完成后把状态设为Done。
我们查询Ready状态的所有流程,按优先级排序,把最高的那个标记为Running,然后执行处理流程,结束后把状态设为Done。
ps  =  db.processes.find({ " status "  :  " READY " ).sort({ " priority "  :  - 1 }).limit( 1 ).next()
db.processes.update({ " _id "  : ps._id}, { " $set "  : { " status "  :  " RUNNING " }})
do_something(ps);
db.processes.update({ " _id "  : ps._id}, { " $set "  : { " status "  :  " DONE " }})
这个算法并不好,会产生资源竞争。假设我们有两个线程来处理,一个线程(线程A)获取了document,另一个线程(线程B)可能在A将状态设置为 Running之前获取同一个document,然后两个线程会执行同一个处理流程。我们可以将检查status作为update的一部分来避免这个问题,不过会变得复杂:
var  cursor  =  db.processes.find({ " status "  :  " READY " }).sort({ " priority "  :  - 1 }).limit( 1 );
while  ((ps  =  cursor.next())  !=   null ) {
ps.update({ " _id "  : ps._id,  " status "  :  " READY " },
{ " $set "  : { " status "  :  " RUNNING " }});
var  lastOp  =  db.runCommand({getlasterror :  1 });
if  (lastOp.n  ==   1 ) {
do_something(ps);
db.processes.update({ " _id "  : ps._id}, { " $set "  : { " status "  :  " DONE " }})
break ;
}
cursor  =  db.processes.find({ " status "  :  " READY " }).sort({ " priority "  :  - 1 }).limit( 1 );
}
这样有另外一个问题,依赖于运行时,一个线程可能处理完所有的工作然后结束,而另一个线程无用的跟在后边。线程A总是能获取处理流程,线程B试图获取同一个处理流程,然后失败,然后看着A完成所有的工作。这种情况就非常适合使用findAndModify命令,findAndModify命令在同一个操作里返回项目并更新它。
>  ps  =  db.runCommand({ " findAndModify "  :  " processes " ,
...  " query "  : { " status "  :  " READY " },
...  " sort "  : { " priority "  :  - 1 },
...  " update "  : { " $set "  : { " status "  :  " RUNNING " }})
{
" ok "  :  1 ,
" value "  : {
" _id "  : ObjectId( " 4b3e7a18005cab32be6291f7 " ),
" priority "  :  1 ,
" status "  :  " READY " 
}
}
Note:返回的document中的状态仍然是Ready,在修饰符生效之前,document已经返回了。
执行find查看就可以看到status被设置为了Running
>  db.processes.findOne({ " _id "  : ps.value._id})
{
" _id "  : ObjectId( " 4b3e7a18005cab32be6291f7 " ),
" priority "  :  1 ,
" status "  :  " RUNNING " 
}
所以我们的程序应该是这个样子:
>  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表示匹配的document会被从collection里删除。
findAndModify命令里各个key的值意义如下
findAndModify: 字符串,collection的名字
query:查询document,检索document的条件
sort:按照什么排序
update:修饰符document,如何更新匹配的document
remove:布尔值,指示是否删除document
new:布尔值,指示返回的document是更新前的还是更新后的,缺省为更新前的。


7.密西西比河此岸的最快书写(The Fastest Write This Side of Mississippi)
本章节所关注的三个操作(insert,update,remove)看起来都是瞬发的,因为它们不会等待服务器的响应。
这并不是异步,应当被看作是"fire-and-forget"型的函数:客户端向服务器发送了document然后就继续自己的事情,客户端从不会收到一个响应诸如“ok,我收到你的消息啦”或者“不ok,你得给我重新发送一次”之类的东西。
安全操作
这些操作的安全版本就是在执行操作之后立刻调用getLastError命令。驱动会等待服务器响应并做相应的处理,通常是抛出一个异常,开发人员可以捕获然后处理。
操作成功之后,getLastError也会返回一些信息,比如update或remove,信息里包含了受影响的document数。
请求和连接
数据库为每个到mongoDB的连接建立一个请求队列,客户端发出一个请求,就会被放到队列的尾部。
注意是一个连接一个队列,如果我们打开两个shell,那么我们有了两个连接,如果我们在一个shell里执行插入,然后在另一个shell里执行查询,有可能得不到刚才插入的document。在同一个shell里执行插入和查询不会有问题,插入的document会被返回。这种情况在使用 Ruby,Python 和Jave驱动时尤其值得注意,因为它们都是用连接池,出于性能上的考虑,这些驱动打开多个连接,然后将请求分配给它们。不过它们本身都有自己的机制,保证一系列的请求会使用一个连接来处理。



转载于:https://my.oschina.net/qiangzigege/blog/196528

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值