MongoDB(operation)

文档的一般操作(增删查改CRUD)

MongoDB自2.6版本起就积极保持文档中的field的相对顺序,而更新某个文档中的field名称可能会导致该文档中的field的重新排序。
write操作包括插入、删除和更新这三个操作,只负责传送出命令,而不管命令是否成功传达(比如突然断网之类的),即不保证绝对成功操作到数据库,所以被认为是“不安全的操作”。当一条操作指令需要操作到多个文档时(bulk操作),对每条文档的操作是原子的,而此条指令引发的所有操作则不是原子的,但是可以使用$isolated操作符来实现(非shard),存储引擎在此操作期间会变成单线程以保证原子性。

关于bulk操作有两种执行情况:

  • 有序执行ordered Bulk Write,速度较慢,如果在执行write操作的过程中抛出了error,前面成功的操作并不会rollback。
  • 无序执行Unordered Bulk Write,如果其中某个write操作出错了,那么其他的操作将继续分别执行。

每次的write操作都可能引起所有index的刷新和调整,这是index给write操作带来的负面影响,所以并不是越多的索引越好,而是越少越好。

插入 insert

db.collection.insertOne() 插入单个文档,也可以插入多个文档(相当bulkWrite用?)。
db.collection.insertMany() 插入多个文档(起相当bulkWrite作用)。
db.collection.insert() 插入单/多个文档。

更新 update

db.collection.updateOne() 更新单个文档。
db.collection.updateMany() 更新多个文档。
db.collection.replaceOne() 替换单个文档。
db.collection.update() 多功能更新工具。
db.collection.findOneAndUpdate() 单文档更新的加强版。
db.collection.findOneAndReplace() 单文档替换的加强版。

删除 delete

db.collection.deleteOne() 删除单个文档
db.collection.deleteMany() 删除多个文档
db.collection.remove() 删除单/多个文档,
db.collection.findOneAndDelete() 删除单个文档的加强版。

额外的操作

db.collection.save() 插入和修改的巧妙结合。
db.collection.bulkWrite() 提供一个操作集合,可以集insertOne、replaceOne、updateOne、updateMany、deleteOne、deleteMany等一系列操作为一身,有序或无序地执行。每单个操作都是原子,而整个bulkWrite操作就不是了。

稍微解释一下基本的操作

  • insert()
功能:在某个集合中插入文档。
格式:db.collection.insert({field:value, ...})
解释:参数只有一个,就是文档{}。
注意:_id键值会自动产生,是一个12字节的Objectid对象,一般由客户端产生,如果没有则服务端产生,有复杂的产生规则。还可以插入多个文档,当writeMany用。
例子:db.test.insert({"name":"joe", "age": 18})
  • update()
功能:搜索出特定的文档,并更新其中的任意部分field元素或整个文档。是原子操作。
格式:db.collect.update( <query>, <update>, { upsert: <boolean>, multi: <boolean>, writeConcern: <document> })
解释:第3个参数是可选的,若无则默认不使用。首参数是查询条件。同find(),次参数是更新项,有多种写法,如$set等。
       第3个参数的upsert项为true:若无匹配<query>,则插入该新文档。若使用了update operator则有会创建新文档,再update它。
       第3个参数的multi项为true:可以使用<query>匹配到所有的文档。
       第3个参数的writeConcern用于感知本次操作的结果,详看官方文档。
注意:小心第2个参数<update>的写法可能替换整个文档,比如下面例子会替换整个文档,除了id。
例子:db.people.update( { name: "Andy" }, { name: "Andy", rating: 1, score: 1 }, { upsert: true })
  • save()
功能:类似insert+update的智能结合,凭借_id参数决定使用哪个,会返回wtriteResult。
格式:db.collection.save(<document>, {writeConcern: <document>})
解释:尾参数可选,一般不用。若无_id项,则调用insert。否则调用upsert。
注意:_id项一般由client产生。此函数是可代替的。
例子:db.products.save( { "item": "book", "price": 40 } )
  • findAndModify()
功能:默认先返回查询的结果,再从数据库中修改。返回的对象是未修改过的对象。
格式:db.collection.findAndModify({
    query: <document>,  可选
    sort: <document>,  可选
    remove: <boolean>,  必要
    update: <document>,  必要
    new: <boolean>,  可选
    fields: <document>,  可选
    upsert: <boolean>,  可选
    bypassDocumentValidation: <boolean>,  可选
    writeConcern: <document>  可选
});
解释:可选参数只要按需提供即可,而必须有remove或update中的一个。速度较慢。
注意:返回的对象已和数据库中的对象可能已经不同了,取决于new。无匹配文档则返回NULL。
例子:db.people.findAndModify( { query: { state: "active" }, sort: { rating: 1 }, remove: true })。
  • find()
    功能:查找指定的文档,返回所有符合条件的文档的cursor,可指定返回文档的field。
    格式find({field:value}, { field1: <boolean>, field2: <boolean> ... } )
    解释:第2个参数是可选的,默认返回整个文档。若指定了域则返回_id域和指定field组成的文档。
    注意:_id项是默认返回的,除非指定为0。第2个参数中还可以不显示指定的field。首参可以指定多个field, 选中同时匹配者。在mongo shell中默认只是显示20条文档,但是也可以将find结果赋值给一个变量,再进行操作的话就可以操作多个选中的文档。因为cursor是可迭代的。MongoDB3.2文档提到,在10分钟内若cursor无活动则默认自动关闭,之后此cursor就读不了文档了,但是可以提前使用cursor.addOption(DBQuery.Option.noTimeout)来关闭默认的超时设置,具体需查看相关driver。
    这里还有个存在的问题,如果使用find之后,数据库中对应的文档已经被修改了,怎么办?在mongodb3.2中已经改变了默认的storage engine MMAPv1 Storage Engine,而是默认使用WiredTiger,具体可查看Default Storage Engine Change。对于MMAPv1来说,如果在此时修改文档,可能会导致同一个文档在一个cursor中返回多次。要解决这个问题,可以查看snapshot mode
    例子1db.products.find( { "price": 25}, { "name": 1, "number": 1 } ).addOption(DBQuery.noTimeout)
    例子2db.products.find( { "price": 25}, { "name": 1, "number": 1 } )

辅助操作

限制 limit() 注:指定返回的文档数量。
忽略 skip() 注:忽略前/后n个文档。
排序 sort() 注:根据各种条件来排序。

  • limit()
    功能:截取前/后n个文档,用于限制返回文档的数量,一般搭配find使用。
    格式db.students.find().limit( n )
    解释:n是一个32位有符号整数。可搭配其他函数来使用。
    注意:.limit(n)之前必须是一个cursor。
  • skip()
    功能:忽略前/后n个文档,用于挑选返回的文档,一般搭配find使用。
    格式db.students.find().skip( n )
    解释:n是一个32位有符号整数。可搭配其他函数来使用。|n|过大会导致效率的下降。
  • sort()
    功能:指定排序规则(即指定field)并对文档进行排序,一般搭配find使用。
    格式db.students.find().sort( {field1: 1, field2: -1, ... } )
    解释:使用1和-1来标识升/降序。limit和skip和sort可以搭配使用,且必须在cursor返回之前使用。BSON类型有默认的比较顺序。与sort相关的操作还很多,请参考官方文档

特定于类型的查询

null类型

  • 一般情况下,你可以这么使用db.collection.find({"num": null}),这样就能将num=null的文档都选中了。但是要小心,其他没有num域的文档也会被匹配中了,也就是说null还能匹配“不存在的”。
  • 如果非要匹配那些num域为null的文档呢?要先用$exists判断该域是否存在,若存在,再进行匹配。一般情况下可以这么做 db.collection.find({"num": { $in : [null] , $exists : true } })或者使用$eq来代替$in也是可以的。

正则表达式

  • 有了正则表达式才是“如虎添翼”地匹配吖。
    比如想忽略大小写,有标识i呀:db.test.find({"name": /joe/i})
  • MOngoDb使用Perl兼容的正则表达式(PCRE)库来匹配正则的。建议在JS shell中验证一下想法,确保正确再使用。
  • 正则表达式还可以匹配自身,假设你直接将{"re": /^qq/}直接存到数据库中,那么使用db.test.find({"re": /^qq/})也可能选中这个文档,

内嵌数组array

  • 数组中的每个元素都可以认为是它的值,只要匹配其中的一个元素,就将文档选中了。比如db.test.find({"array": "element2"})
  • 匹配多个元素,甚至整个array,可以使用$all操作符,并不需要关心顺序问题。比如{ tags : { $all: [ "ssl" , "security" ] } } 就等同于 { $and: [ { tags: "ssl" }, { tags: "security" } ] }
  • 匹配整个array,可以直接将整个array写出来吖。值得注意的是,只有顺序也对应上了才会选中。
  • 对元素在array中的位置很关心。可以使用 array.index语法指定下标,比如db.test.find("fruits.2": "apple"),指定了fruits[2]=”apple”,注意下标是从0开始的。

内嵌文档

  • 查询内嵌文档。查询内嵌文档并不同于内嵌数组array一样,但有几分相似。
  • 查询方式(1): 完整匹配,即将整个内嵌文档列出来,且顺序一致。
    db.test.find({ "post": {"author": "joe", "size": 50} })
  • 查询方式(2):使用$elemMatch(query),可以不管次序地匹配部分field。单field就需要用了。
    db.scores.find( { results: { $elemMatch: { $gte: 80, $lt: 85 } } })
  • 查询方式(3):使用dot标识,以内嵌文档名.field来标识内嵌文档中的一个field,而不管次序。
    db.test.find({ "post.author": "joe", "post.size": 50} })
  • 注意:

关于读操作(read operation)

游标 cursor

  • 通过find操作所返回的是一个cursor,每次可以从中读取一条文档,直到无文档可读。当在mongo shell中使用find的时候,如果所选中的文档超过20条,会默认读取20条文档,当然,可以通过DBQuery.shellBatchSize来修改这个数字,参考Executing Queries
  • 默认情况下,假设一个cursor还没有迭代完毕,而在10分钟无活动MongoDB就会关闭这个cursor,这个可以通过例如var myCursor = db.inventory.find().addOption(DBQuery.Option.noTimeout);来关闭这个规定。因为cursor中的文档是分批到达的,比如大多数情况下第一批是101条文档或者是仅足够超过1mb的大小,余下的批次都是以4mb为单位到达的。每当从一个cursor中读完一批后,客户端会发送请求到server,然后接收下一批查询到的数据。要改变batchsize=4mb的情况,可以参考batchSize()和limit()。
  • 如果find之后还添加一个无指定index的sort操作,那么server会一次性读取加载选中的所有文档进内存再进行排序。
  • 如果cursor中的文档还没有读取完毕,cursor.next()可以返回下一个文档。
  • 判断一个cursor是否还有文档未读取,可以使用cursor.hasNext()
  • 想查看当前批次中还有多少条文档,参考objsLeftInBatch()
  • 当前批次已经读取完毕,会调用getmore
    操作。
  • 想查看某个cursor的信息,可以参考 db.serverStatus()
  • cursor具有如下这些操作:
    cursor.batchSize()
    设置server每次发送多少的文档到客户端。
    cursor.close() 关闭游标。
    cursor.comment()
    对此次查询做一些简短记录以方便问题追溯。
    cursor.count()
    改成返回文档数,而不是返回文档。
    cursor.explain()
    调出查询结果的信息。
    cursor.forEach()
    对每个返回的文档都应用一次。
    cursor.hasNext()
    判断cursor是否已经读取完毕。
    cursor.hint()
    强制指定一个index进行查询,而不是让引擎自动选择。
    cursor.itcount()
    通过不断向server发送消息,在客户端做文档数统计。
    cursor.limit()
    限制返回的文档数。
    cursor.map()
    对每个文档都应用到一个函数中,并将函数的返回值收集到一个array中。
    cursor.maxScan()
    当效率比较重要时,指定最大的扫描item/集合/索引数。
    cursor.maxTimeMS()
    指定一个游标的不活动存活时间间隔长度。
    cursor.max()
    指定索引用的field的上界。
    cursor.min()
    指定索引用的field的下界。
    cursor.next()
    读取下一个文档。
    cursor.noCursorTimeout()
    告诉server关闭timeout。
    cursor.objsLeftInBatch()
    获取客户端当前batch中还剩下多少文档未读取,batch读完就要发送消息给server索取文档。
    cursor.pretty()
    设置cursor中结果的显示格式。
    cursor.readConcern()
    指定一个read concern。
    cursor.readPref()
    Specifies a read preference to a cursor to control how the client directs queries to a replica set.
    cursor.returnKey()
    指示让cursor返回index keys而不是文档。
    cursor.showRecordId()
    为每个选中的文档添加一个内部的存储引擎ID域。
    cursor.size()
    统计在应用了skip()和limit()之后选中的文档数量。
    cursor.skip()
    跳过find结果的前/后的n条文档。
    cursor.snapshot()
    强迫cursor使用的index是_id,这样就保证了每个文档只返回一次,即使文档中途被修改。
    cursor.sort() 对选中文档进行排序。
    cursor.tailable() Marks the cursor as tailable. Only valid for cursors over capped collections.
    cursor.toArray() 将选中的文档塞进一个array返回。

Query(查询)

  • 慎用$nin、$ne等操作符,因为它在使用的时候需要进行大量的匹配,有时甚至比直接检索整个集合的效果更差。

问题

</br>

problem 1:
假设有如下的批量update操作,再假设服务器都完成了所有update中包含的query操作(mongodb仅保证插入document时的原子性),那么就可能会导致插入很多个_id不一样的document了,因为带了upsert参数。

db.people.update( 
  { name: "Andy" }, 
  { name: "Andy", rating: 1, score: 1 }, 
  { upsert: true }
)

解决方法:可以尝试为name域设置一个unique index,这样就只会有一个update能够成功插入。

problem 2:
假设有如下的update操作,query表达式中带有_id.uid(有个点),这样如果查询失败了,因为upsert为true,所以插入也失败了。

db.collection.update( 
  { "_id.name": "Robert Frost", "_id.uid": 0 }, 
  { "categories": ["poet", "playwright"] }, 
  { upsert: true } 
)

解决方法:查询中带的_id域不能带点。

    原文作者:我看不见
    原文地址: https://www.jianshu.com/p/30ffe0894305
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞