MongoDB(operation)


文檔的一般操作(增刪查改CRUD)

MongoDB自2.6版本起就積極保持文檔中的field的相對順序,而更新某個文檔中的field名稱可能會導致該文檔中的field的重新排序。
write操作包括插入、刪除和更新這三個操作,只負責傳送出命令,而不管命令是否成功傳達(比如突然斷網(wǎng)之類的),即不保證絕對成功操作到數(shù)據(jù)庫,所以被認為是“不安全的操作”。當一條操作指令需要操作到多個文檔時(bulk操作),對每條文檔的操作是原子的,而此條指令引發(fā)的所有操作則不是原子的,但是可以使用$isolated操作符來實現(xiàn)(非shard),存儲引擎在此操作期間會變成單線程以保證原子性。

關于bulk操作有兩種執(zhí)行情況:

  • 有序執(zhí)行ordered Bulk Write,速度較慢,如果在執(zhí)行write操作的過程中拋出了error,前面成功的操作并不會rollback。
  • 無序執(zhí)行Unordered Bulk Write,如果其中某個write操作出錯了,那么其他的操作將繼續(xù)分別執(zhí)行。

每次的write操作都可能引起所有index的刷新和調(diào)整,這是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等一系列操作為一身,有序或無序地執(zhí)行。每單個操作都是原子,而整個bulkWrite操作就不是了。


稍微解釋一下基本的操作

  • insert()
功能:在某個集合中插入文檔。
格式:db.collection.insert({field:value, ...})
解釋:參數(shù)只有一個,就是文檔{}。
注意:_id鍵值會自動產(chǎn)生,是一個12字節(jié)的Objectid對象,一般由客戶端產(chǎn)生,如果沒有則服務端產(chǎn)生,有復雜的產(chǎn)生規(guī)則。還可以插入多個文檔,當writeMany用。
例子:db.test.insert({"name":"joe", "age": 18})
  • update()
功能:搜索出特定的文檔,并更新其中的任意部分field元素或整個文檔。是原子操作。
格式:db.collect.update( <query>, <update>, { upsert: <boolean>, multi: <boolean>, writeConcern: <document> })
解釋:第3個參數(shù)是可選的,若無則默認不使用。首參數(shù)是查詢條件。同find(),次參數(shù)是更新項,有多種寫法,如$set等。
       第3個參數(shù)的upsert項為true:若無匹配<query>,則插入該新文檔。若使用了update operator則有會創(chuàng)建新文檔,再update它。
       第3個參數(shù)的multi項為true:可以使用<query>匹配到所有的文檔。
       第3個參數(shù)的writeConcern用于感知本次操作的結果,詳看官方文檔。
注意:小心第2個參數(shù)<update>的寫法可能替換整個文檔,比如下面例子會替換整個文檔,除了id。
例子:db.people.update( { name: "Andy" }, { name: "Andy", rating: 1, score: 1 }, { upsert: true })
  • save()
功能:類似insert+update的智能結合,憑借_id參數(shù)決定使用哪個,會返回wtriteResult。
格式:db.collection.save(<document>, {writeConcern: <document>})
解釋:尾參數(shù)可選,一般不用。若無_id項,則調(diào)用insert。否則調(diào)用upsert。
注意:_id項一般由client產(chǎn)生。此函數(shù)是可代替的。
例子:db.products.save( { "item": "book", "price": 40 } )
  • findAndModify()
功能:默認先返回查詢的結果,再從數(shù)據(jù)庫中修改。返回的對象是未修改過的對象。
格式:db.collection.findAndModify({
    query: <document>,  可選
    sort: <document>,  可選
    remove: <boolean>,  必要
    update: <document>,  必要
    new: <boolean>,  可選
    fields: <document>,  可選
    upsert: <boolean>,  可選
    bypassDocumentValidation: <boolean>,  可選
    writeConcern: <document>  可選
});
解釋:可選參數(shù)只要按需提供即可,而必須有remove或update中的一個。速度較慢。
注意:返回的對象已和數(shù)據(jù)庫中的對象可能已經(jīng)不同了,取決于new。無匹配文檔則返回NULL。
例子:db.people.findAndModify( { query: { state: "active" }, sort: { rating: 1 }, remove: true })。
  • find()
    功能:查找指定的文檔,返回所有符合條件的文檔的cursor,可指定返回文檔的field。
    格式find({field:value}, { field1: <boolean>, field2: <boolean> ... } )
    解釋:第2個參數(shù)是可選的,默認返回整個文檔。若指定了域則返回_id域和指定field組成的文檔。
    注意:_id項是默認返回的,除非指定為0。第2個參數(shù)中還可以不顯示指定的field。首參可以指定多個field, 選中同時匹配者。在mongo shell中默認只是顯示20條文檔,但是也可以將find結果賦值給一個變量,再進行操作的話就可以操作多個選中的文檔。因為cursor是可迭代的。MongoDB3.2文檔提到,在10分鐘內(nèi)若cursor無活動則默認自動關閉,之后此cursor就讀不了文檔了,但是可以提前使用cursor.addOption(DBQuery.Option.noTimeout)來關閉默認的超時設置,具體需查看相關driver。
    這里還有個存在的問題,如果使用find之后,數(shù)據(jù)庫中對應的文檔已經(jīng)被修改了,怎么辦?在mongodb3.2中已經(jīng)改變了默認的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() 注:指定返回的文檔數(shù)量。
忽略 skip() 注:忽略前/后n個文檔。
排序 sort() 注:根據(jù)各種條件來排序。

  • limit()
    功能:截取前/后n個文檔,用于限制返回文檔的數(shù)量,一般搭配find使用。
    格式db.students.find().limit( n )
    解釋:n是一個32位有符號整數(shù)。可搭配其他函數(shù)來使用。
    注意:.limit(n)之前必須是一個cursor。
  • skip()
    功能:忽略前/后n個文檔,用于挑選返回的文檔,一般搭配find使用。
    格式db.students.find().skip( n )
    解釋:n是一個32位有符號整數(shù)。可搭配其他函數(shù)來使用。|n|過大會導致效率的下降。
  • sort()
    功能:指定排序規(guī)則(即指定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/}直接存到數(shù)據(jù)庫中,那么使用db.test.find({"re": /^qq/})也可能選中這個文檔,

內(nèi)嵌數(shù)組array

  • 數(shù)組中的每個元素都可以認為是它的值,只要匹配其中的一個元素,就將文檔選中了。比如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開始的。

內(nèi)嵌文檔

  • 查詢內(nèi)嵌文檔。查詢內(nèi)嵌文檔并不同于內(nèi)嵌數(shù)組array一樣,但有幾分相似。
  • 查詢方式(1): 完整匹配,即將整個內(nèi)嵌文檔列出來,且順序一致。
    db.test.find({ "post": {"author": "joe", "size": 50} })
  • 查詢方式(2):使用$elemMatch(query),可以不管次序地匹配部分field。單field就需要用了。
    db.scores.find( { results: { $elemMatch: { $gte: 80, $lt: 85 } } })
  • 查詢方式(3):使用dot標識,以內(nèi)嵌文檔名.field來標識內(nèi)嵌文檔中的一個field,而不管次序。
    db.test.find({ "post.author": "joe", "post.size": 50} })
  • 注意:

關于讀操作(read operation)

游標 cursor

  • 通過find操作所返回的是一個cursor,每次可以從中讀取一條文檔,直到無文檔可讀。當在mongo shell中使用find的時候,如果所選中的文檔超過20條,會默認讀取20條文檔,當然,可以通過DBQuery.shellBatchSize來修改這個數(shù)字,參考Executing Queries
  • 默認情況下,假設一個cursor還沒有迭代完畢,而在10分鐘無活動MongoDB就會關閉這個cursor,這個可以通過例如var myCursor = db.inventory.find().addOption(DBQuery.Option.noTimeout);來關閉這個規(guī)定。因為cursor中的文檔是分批到達的,比如大多數(shù)情況下第一批是101條文檔或者是僅足夠超過1mb的大小,余下的批次都是以4mb為單位到達的。每當從一個cursor中讀完一批后,客戶端會發(fā)送請求到server,然后接收下一批查詢到的數(shù)據(jù)。要改變batchsize=4mb的情況,可以參考batchSize()和limit()。
  • 如果find之后還添加一個無指定index的sort操作,那么server會一次性讀取加載選中的所有文檔進內(nèi)存再進行排序。
  • 如果cursor中的文檔還沒有讀取完畢,cursor.next()可以返回下一個文檔。
  • 判斷一個cursor是否還有文檔未讀取,可以使用cursor.hasNext()
  • 想查看當前批次中還有多少條文檔,參考objsLeftInBatch()
  • 當前批次已經(jīng)讀取完畢,會調(diào)用getmore
    操作。
  • 想查看某個cursor的信息,可以參考 db.serverStatus()
  • cursor具有如下這些操作:
    cursor.batchSize()
    設置server每次發(fā)送多少的文檔到客戶端。
    cursor.close() 關閉游標。
    cursor.comment()
    對此次查詢做一些簡短記錄以方便問題追溯。
    cursor.count()
    改成返回文檔數(shù),而不是返回文檔。
    cursor.explain()
    調(diào)出查詢結果的信息。
    cursor.forEach()
    對每個返回的文檔都應用一次。
    cursor.hasNext()
    判斷cursor是否已經(jīng)讀取完畢。
    cursor.hint()
    強制指定一個index進行查詢,而不是讓引擎自動選擇。
    cursor.itcount()
    通過不斷向server發(fā)送消息,在客戶端做文檔數(shù)統(tǒng)計。
    cursor.limit()
    限制返回的文檔數(shù)。
    cursor.map()
    對每個文檔都應用到一個函數(shù)中,并將函數(shù)的返回值收集到一個array中。
    cursor.maxScan()
    當效率比較重要時,指定最大的掃描item/集合/索引數(shù)。
    cursor.maxTimeMS()
    指定一個游標的不活動存活時間間隔長度。
    cursor.max()
    指定索引用的field的上界。
    cursor.min()
    指定索引用的field的下界。
    cursor.next()
    讀取下一個文檔。
    cursor.noCursorTimeout()
    告訴server關閉timeout。
    cursor.objsLeftInBatch()
    獲取客戶端當前batch中還剩下多少文檔未讀取,batch讀完就要發(fā)送消息給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()
    為每個選中的文檔添加一個內(nèi)部的存儲引擎ID域。
    cursor.size()
    統(tǒng)計在應用了skip()和limit()之后選中的文檔數(shù)量。
    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參數(shù)。

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域不能帶點。


最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容