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替换,如 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"键的值。 下边我们看看常用的更新修饰符
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的值意义如下
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驱动时尤其值得注意,因为它们都是用连接池,出于性能上的考虑,这些驱动打开多个连接,然后将请求分配给它们。不过它们本身都有自己的机制,保证一系列的请求会使用一个连接来处理。 |
|