MongoDB 參考手冊

學習《MongoDB 權(quán)威指南·第2版》的筆記髓抑,結(jié)合 MongoDB 官方最新文檔(v3.6)袋坑,簡單記錄一些概念仗处、注意點、語法糖枣宫,方便使用的時候快速查閱疆柔、參考。

1. 文檔

文檔是 MongoDB 中數(shù)據(jù)的基本單元镶柱,非常類似于關(guān)系型數(shù)據(jù)庫中的行。
文檔的鍵是字符串模叙,除了少數(shù)例外情況歇拆,鍵可以使用任意 UTF-8 字符:

  • 不能含有 \0 (空字符),這個字符用于表示鍵的結(jié)尾
  • . 和 $ 具有特殊意義范咨,是被保留的故觅,只能在特定環(huán)境下使用
  • MongoDB 不但區(qū)分類型,而且區(qū)分大小寫渠啊。例如输吏,下面的兩個文檔是不同的:
    {"foo" : 3}
    {"foo" : "3"}
    
  • 文檔不能有重復的鍵

2. 集合

集合就是一組文檔。如果將 MongoDB 中的一個文檔比喻為關(guān)系型數(shù)據(jù)庫中的一行替蛉,那么一個集合就相當于一張表贯溅。

  • 集合名不能以 “system.” 開頭,這是為系統(tǒng)集合保留的前綴
  • 可以使用 db.collectionName 獲取一個集合的全路徑名躲查,但是它浅,如果集合名稱中包含保留字或者無效的 JavaScript 屬性名稱,db.collectionName 就不能正常工作了镣煮。這個時候需要通過函數(shù) db.getCollection( "collectionName" ) 訪問相應的集合姐霍,或者使用數(shù)組訪問語法。 比如典唇,某個怪異的集合名稱為 db.@#&!镊折,要訪問這個集合的話,可以數(shù)組訪問方式:
    > var name = "@#&!"
    > db[name].find()
    
  • 普通集合是動態(tài)創(chuàng)建的介衔,而且可以自動增長以容納更多的數(shù)據(jù)恨胚。比如下面插入語句,如果集合 foo 不存在炎咖,則會自動創(chuàng)建該集合
    > db.foo.insert({"x" : 1})
    
  • MongoDB 中還有另一種不同類型的集合:固定集合与纽。固定集合需要事先創(chuàng)建侣签,同時指定它的大小,一旦創(chuàng)建成功后大小不能再改變急迂。固定集合類似于循環(huán)隊列影所,當空間被占滿時,如果再插入新文檔僚碎,固定集合會自動將最老的文檔刪除以釋放空間猴娩,新文檔會占據(jù)這塊釋放出來的空間。
    > // 創(chuàng)建一個名為 my_collection 大小為 100000 字節(jié)的固定集合
    > db.createCollection( "my_collection", { "capped" : true, "size" : 100000 } )
    > // 創(chuàng)建一個名為 my_collection2 大小為 100000 字節(jié)的固定集合勺阐,集合最多容納 100 個文檔
    > db.createCollection( "my_collection2", { "capped" : true, "size" : 100000, "max" : 100 } )
    

3. 數(shù)據(jù)庫

多個文檔組成集合卷中,多個集合組成數(shù)據(jù)庫。

  • 數(shù)據(jù)庫名區(qū)分大小寫渊抽,即便是在不區(qū)分大小寫的文件系統(tǒng)中也是如此蟆豫。簡單起見,數(shù)據(jù)庫名全部小寫
  • 數(shù)據(jù)庫最終會變成文件系統(tǒng)里的文件懒闷,而數(shù)據(jù)庫名就是相應的文件名十减,這就是數(shù)據(jù)庫名有限制的原因
  • 系統(tǒng)保留數(shù)據(jù)庫
    • admin
      從身份驗證的角度來講,這是 “root” 數(shù)據(jù)庫愤估。如果將一個用戶添加到 admin 數(shù)據(jù)庫帮辟,那么這個用戶將自動獲得所有數(shù)據(jù)庫的權(quán)限。再者玩焰,一些特定的服務(wù)器端命令也只能從 admin 數(shù)據(jù)庫運行
    • local
      這個數(shù)據(jù)庫永遠不可以復制由驹,且一臺服務(wù)器上的所有本地集合都可以存儲在這個數(shù)據(jù)庫
    • config
      用于分片設(shè)置時,分片信息會存儲在此數(shù)據(jù)庫中

4. MongoDB shell

MongoDB 自帶 JavaScript shell昔园,可在 shell 中使用命令行與 MongoDB 實例交互

  • shell 連接到 MongoDB 實例

    $ mongo some-host:27017/mydb
    MongoDB shell version v3.6.1
    connecting to: mongodb://some-host:27017/mydb
    MongoDB server version: 3.6.1
    mongos>  
    
  • 如果想 shell 啟動時不連接到任何 mongod 實例蔓榄,可通過參數(shù) --nodb 實現(xiàn):

    $ mongo --nodb
    MongoDB shell version v3.6.1
    Welcome to the MongoDB shell.
    For interactive help, type "help".
    For more comprehensive documentation, see
            http://docs.mongodb.org/
    Questions? Try the support group
            http://groups.google.com/group/mongodb-user
    >  
    

    啟動之后,在需要時運行 new Mongo(hostname) 命令就可以連接到想要的 mongod 了:

    > conn = new Mongo("some-host:27017")
    connection to some-host:27017
    > db = conn.getDB("mydb")
    mydb
    
  • 使用 shell 執(zhí)行腳本默刚。除了以交互式使用 shell 在润樱,還可以將 JavaScript 腳本直接傳給 shell

    $ mongo script1.js script2.js
    MongoDB shell version v3.6.1
    connecting to: mongodb://127.0.0.1:27017
    MongoDB server version: 3.6.1
    I am script1
    I am script2
    $
    
  • 如果希望在指定的主機/端口上的 mongod 運行腳本,需要指定地址羡棵,然后再跟上腳本文件名

    $ mongo --quiet some-host:3000/mydb script1.js script2.js
    

    --quiet可以讓 shell 不打印 “MongoDB shell version...” 提示

  • 在交互模式下壹若,如果想執(zhí)行某個腳本文件時,可以使用 load() 函數(shù):

    mongos> load("script1.js")
    I am script1
    mongos> 
    
  • 在腳本中可以訪問 db 變量皂冰,以及其他全局變量店展。然而,shell 輔助函數(shù)(比如 "use db" 和 "show collections")不可以在文件中使用秃流。這些輔助函數(shù)都有對應的 JavaScript 函數(shù)赂蕴,如下表:

    輔助函數(shù) 等價函數(shù)
    use foo db.getSisterDB("foo")
    show dbs db.getMongo().getDBs()
    show collections db.getCollectionNames()
  • 如果在 shell 中想執(zhí)行命令行程序,那么可以通過 run() 來實現(xiàn)舶胀。比如想查看某個文件夾下的文件列表:

    mongos> run("ls", "-l", "/home/myUser/my-scripts/")
    
  • .mongorc.js文件
    如果某些腳本會被頻繁加載概说,可以將它們添加到 .mongorc.js 文件中碧注,這個文件會在啟動 shell 時自動允許。
    如果想禁止加載此文件糖赔,可以在 shell 啟動時指定參數(shù) --norc 參數(shù)萍丐。

  • 定制 shell 提示
    將 prompt 變量設(shè)為一個字符串或者函數(shù),就可以重寫默認的 shell 提示放典。比如提示當前時間:

    prompt = function() {
        return (new Date()) + "> ";
    };
    

    shell 啟動后:

    Fri Jan 05 2018 00:20:55 GMT-500 (EST)> 
    

5. 查詢

  • 查詢集合中的所有文檔逝变,返回所有鍵/值對

    > // 若不指定查詢文檔參數(shù),結(jié)果就返回集合 coll 中的所有文檔
    > db.coll.find()
    
  • 簡單條件查詢
    find() 的第一個參數(shù)決定了要返回哪些文檔奋构,這個參數(shù)是一個文檔壳影,用于指定查詢條件∶志剩可以向查詢文檔加入多個鍵/值對宴咧,將多個查詢條件組合在一起,這樣的查詢條件會被解釋成 “條件1 AND 條件2 AND ... 條件N”径缅。

    > // 查詢名字為 dereck 且年齡為 27 的用戶
    > db.users.find({"name" : "dereck", "age" : 27})
    
  • 指定需要返回的鍵
    find() 的第二個參數(shù)用來指定想要的鍵掺栅。只返回需要的鍵的好處是,既會節(jié)省傳輸?shù)臄?shù)據(jù)芥驳,又能節(jié)省客戶端解碼文檔的時間和內(nèi)存消耗。

    > db.users.find({}, {"name" : 1, "age" : 1})
    

    1 表示需要返回該鍵茬高,0 表示不返回

  • 比較操作符
    "$lt"兆旬、"$lte"、"$gt"怎栽、"$gte"丽猬、"$ne" 分別對應 <、<=熏瞄、>脚祟、>=、!=强饮。

    > // 查詢 18~30 歲(含)的用戶
    > db.users.find({ "age" : { "$gte" : 18, "$lte" : 30 } })
    
  • OR 查詢
    有兩種方式進行 OR 查詢:"in" 用來對**單個**鍵進行 OR 查詢由桌;"or" 除了可以單個鍵,還可以用來對多個鍵進行 OR 查詢邮丰。

    > // "$in" 非常靈活行您,可以指定不同類型的條件和值
    > db.users.find({ "user_id" : { "$in" : [ 12345, "dereck" ] })
    
    > // "$or" 接受一個包含所有可能條件的數(shù)組作為參數(shù)
    > db.users.find({ "$or" : [ {"country" : "China"}, {"city" : "Shanghai"} ] })
    

    當某個查詢既可以使用 "in" 也是使用 "or" 完成時,優(yōu)先使用 "in" 查詢剪廉,因為 "in" 只執(zhí)行單次查詢娃循, 而 "or" 需要執(zhí)行多次查詢(比如,{"in":[ 12345, "dereck" ]} 需要兩次)后將結(jié)果合并再返回斗蒋,效率較低捌斧。如果不得不使用 "or" 笛质,MongoDB 需要檢查每次查詢的結(jié)果集并且從中移除重復的文檔(有些文檔可能會被多個 "or" 字句匹配到)。

  • not "not" 是元條件捞蚂,可以用在任何其他條件之上

  • null 類型的查詢
    null 不僅會匹配某個鍵的值是 null妇押,而且還會匹配不包含這個鍵的文檔。
    假設(shè)集合 c 中包含如下三條文檔:

    > db.c.find({}, {"_id" : 0})
    { "y" : null }
    { "y" : 1 }
    { "y" : 2 }
    

    查詢 "y" 鍵為 null 的文檔洞难,可以得到預期的一條文檔:

    > db.c.find({"y" : null}, {"_id" : 0})
    { "y" : null }
    

    查詢 "z" 鍵為 null 的文檔舆吮,結(jié)果返回了整個集合,因為所有文檔都沒有 "z" 鍵:

    > db.c.find({"z" : null}, {"_id":0})
    { "y" : null }
    { "y" : 1 }
    { "y" : 2 }
    

    如果想要結(jié)果僅返回包含指定鍵且鍵值為 null 的文檔的話队贱,需要通過 "$exists" 條件判定鍵值已存在:

    > db.c.find({"z" : {"$in" : [null], "$exists" : true}},  {"_id":0})
    
  • 數(shù)組查詢

    • 通過 "$all" 來實現(xiàn)對數(shù)組的多個元素進行匹配色冀。假設(shè)集合 food 包含以下三條文檔:
    > db.food.find({}, {"_id" : 0})
    { "fruit" : [ "apple", "banana", "peach" ] }
    { "fruit" : [ "apple", "kumquat", "orange" ] }
    { "fruit" : [ "cherry", "banana", "apple" ] }
    

    找到既有 "apple" 又有 "banana" 的文檔:

    > // "apple"、 "banana" 的先后順序無關(guān)緊要
    > db.food.find({"fruit" : {"$all" : [ "apple", "banana" ]}}, {"_id" : 0})
    { "fruit" : [ "apple", "banana", "peach" ] }
    { "fruit" : [ "cherry", "banana", "apple" ] }
    
    • 可以使用 key.index 語法指定下標柱嫌,來查詢數(shù)組特定位置的元素锋恬,下標從 0 開始:
    > db.food.find({"fruit.2" : "peach"}, {"_id" : 0})
    { "fruit" : [ "apple", "banana", "peach" ] }
    
    • 使用 "$size" 查詢特定長度的數(shù)組
    > db.food.find({"fruit" : {"$size : 3"}}, {"_id" : 0})
    { "fruit" : [ "apple", "banana", "peach" ] }
    { "fruit" : [ "apple", "kumquat", "orange" ] }
    { "fruit" : [ "cherry", "banana", "apple" ] }
    
    • 在 find() 的第二個參數(shù)中,通過使用 "$slice" 操作符返回某個鍵匹配的數(shù)組元素的一個子集:

    通過 { "key" : n} 返回數(shù)組 "key" 的前 n 個元素:

    > // 每個文檔返回前 2 個水果
    > db.food.find({}, {"_id" : 0, "fruit" : {"$slice" : 2}})
    { "fruit" : [ "apple", "banana" ] }
    { "fruit" : [ "apple", "kumquat" ] }
    { "fruit" : [ "cherry", "banana" ] }
    

    通過 { "key" : -n} 返回數(shù)組 "key" 的后 n 個元素:

    > // 每個文檔返回后 2 個水果
    > db.food.find({}, {"_id" : 0, "fruit" : {"$slice" : -2}})
    { "fruit" : [ "banana", "peach" ] }
    { "fruit" : [ "kumquat", "orange" ] }
    { "fruit" : [ "banana", "apple" ] }
    

    通過 { "key" : [start, end] } 返回數(shù)組 "key" 的中間部分元素编丘,如果數(shù)組元素不足与学,則返回 start 后的所有元素:

    > db.food.insert({"fruit" : [ "cherry", "banana", "apple", "orange" ]})
    WriteResult({ "nInserted" : 1 })
    > db.food.find({}, {"_id" : 0, "fruit" : { "$slice" : [2, 3] }}
    { "fruit" : [ "peach" ] }
    { "fruit" : [ "orange" ] }
    { "fruit" : [ "apple", "orange" ] }
    
  • 內(nèi)嵌文檔查詢
    通過點表示法查詢內(nèi)嵌文檔。比如查詢?nèi)缦挛臋n:

    {
        "name" : {
            "first" : "Dereck",
            "last" : "Yu"
        },
        "age" : 29
    }
    

    通過點表示法針對特定的鍵進行查詢:

    > db.people.find({ "name.first" :"Dereck", "name.last" : "Yu" }, {"_id" : 0})
    { "name" : { "first" : "Dereck", "last" : "Yu"}, "age" : 29 }
    
  • 內(nèi)嵌文檔的數(shù)組元素的查詢
    如果內(nèi)嵌文檔是一個數(shù)組嘉抓,那么上述“點表示法”查詢可能就得不到正確的結(jié)果了索守。比如以下 blog 集合中查詢由 "dereck" 發(fā)表的 5 分以上的評論:

    {
        "content" : "hello world",
        "comments" : [
            {
                "author" : "dereck",
                "score" : 3,
                "comment" : "terrible post"
            },
            {
                "author" : "sherry",
                "score" : 6,
                "comment" : "nice post"
            }
        ]
    }
    

    嘗試使用“點表示法”查詢:

    > // 下面這條查詢語句返回了所有結(jié)果,因為 author 條件在第一條評論中符合了抑片,而 score 條件在第二條評論中符合了卵佛,所以兩條評論都被返回
    > db.blog.find({ "comments.author" : "dereck", "comments.score" : {"$gte" : 5} }, {"_id" : 0}).pretty()
    {
        "content" : "hello world",
        "comments" : [
            {
                "author" : "dereck",
                "score" : 3,
                "comment" : "terrible post"
            },
            {
                "author" : "sherry",
                "score" : 6,
                "comment" : "nice post"
            }
        ]
    }
    

    要匹配數(shù)組中的單個內(nèi)嵌文檔,需要使用 "$elemMatch" 將限定條件進行分組敞斋,對單個內(nèi)嵌文檔中的多個鍵進行匹配

    > db.blog.find({ "comments" : { "$elemMatch" : { "author" : "dereck", "score" : { "$gte" : 5 } } } }, {"_id" : 0})
    
  • where 查詢 鍵/值對是一種表達能力非常好的查詢方式截汪,但有些需求不能通過這種方式表達,此時可以使用 "where" 語句植捎。 "where" 可以在查詢中執(zhí)行任意的 JavaScript 衙解,因此為了安全起見,應該嚴格限制或者消除 "where" 語句的使用焰枢,應該禁止終端用戶使用任意的 "$where" 語句蚓峦。
    假設(shè)有如下文檔,需要返回兩個鍵具有相同值的文檔:

    > db.foo.insert({ "x" : 1, "y" : 2, "z" : 3 })
    > db.foo.insert({ "x" : 2, "y" : 2, "z" : 4 })
    >
    > // 第二個文檔中济锄,"x" 和 "y" 的值相同枫匾,所以應該返回比文檔
    > db.foo.find({ "$where" : function() {
    ... for (var key1 in this) {
    ...     for (var key2 in this) {
    ...         if ( key1 != key2 && this[key1] == this[key2] )
    ...             return true;
    ...     }
    ... }
    ... return false;
    ... } }):
    { "_id" : ObjectId("5a54647675b6e8450f1d5fd2"), "x" : 2, "y" : 2, "z" : 4 }
    

    不是非常必要時,一定要避免使用 "where" 查詢拟淮,它不能使用索引干茉,每個文檔都要從 BSON 轉(zhuǎn)換成 JavaScript 對象,然后通過 "where" 表達式來運行很泊,在速度上要比常規(guī)查詢慢很多角虫。

  • limit
    要限制結(jié)果數(shù)量沾谓,可在 find() 后使用 limit() 函數(shù),limit 指定的是上限戳鹅,而非下限均驶。

    > // 只返回 3 個結(jié)果,如果匹配的結(jié)果不足 3 個枫虏,則返回所有
    > db.c.find().limit(3)
    
  • skip
    略過前面 N 個匹配的文檔妇穴,返回余下的文檔,如果集合里面能匹配的文檔不足需要略掉的數(shù)量隶债,則不會返回任何文檔腾它。

    > db.c.find().skip(3)
    

    避免使用 skip 略過大量結(jié)果,數(shù)量非常多的話會變得非常慢死讹,因為它要先找到需要被略過的數(shù)據(jù)瞒滴,然后再拋棄這些數(shù)據(jù)

  • sort
    接受一個對象作為參數(shù),這個對象是一組鍵值對赞警,鍵對應文檔的鍵名妓忍,值代表排序的方向。排序方向可以是 1 (升序)或者 -1 (降序)愧旦。如果指定了多個鍵世剖,則按照這些鍵被指定的順序逐個排序。

    > db.c.find().sort({ "name" : 1, "age" : -1 })
    

6. 創(chuàng)建笤虫、更新旁瘫、刪除文檔

  • 插入單個文檔

    > db.foo.insert({"x" : 1})
    WriteResult({ "nInserted" : 1 })
    

    從 3.2 版本開始,可以使用 "insertOne()" 方法:

    > db.foo.insertOne({"y" : 1})
    {
        "acknowledged" : true,
        "insertedId" : ObjectId("5a57002cbfe1dc293187ec6d")
    }
    
  • 批量插入多個文檔
    如果要向集合中插入多個文檔耕皮,批量插入會快一些境蜕,一次發(fā)送數(shù)十蝙场、數(shù)百乃至數(shù)千個文檔會明顯提高插入的速度

    > db.foo.batchInsert([{"x" : 1}, {"y" : 2},  {"z" : 3}])
    

    如果在執(zhí)行批量插入的過程中有一個文檔插入失敗凌停,那么在這個文檔之前的所有文檔都會成功插入到集合中,而這個文檔以及之后的所有文檔全部插入失敗售滤。如果希望 batchInsert 忽略錯誤并且繼續(xù)執(zhí)行后續(xù)插入罚拟,可以使用 continueOnError 選項。 shell 并不支持這個選項完箩,但所有的驅(qū)動程序都支持赐俗。

    從 3.2 版本開始,可以使用 "insertMany()" 方法:

    > db.foo.insertMany([
    ...     { "a" : 10 },
    ...     { "a" : 11 },
    ...     { "a" : 12 }
    ... ])
    {
        "acknowledged" : true,
        "insertedId" : [
            ObjectId("5a570a6dbef1dc293187ec6e"),
            ObjectId("5a570a6dbef1dc293187ec6f"),
            ObjectId("5a570a6dbef1dc293187ec70")
        ]
    }
    
  • 刪除文檔
    刪除數(shù)據(jù)是永久性的弊知,不能撤銷阻逮,也不能恢復
    刪除集合中的所有文檔:

    > db.foo.remove({})
    

    刪除符合條件的文檔:

    > db.employee.remove({"department" : "A"})
    

    remove() 函數(shù)會刪除文檔數(shù)據(jù),但是不會刪除集合本身秩彤,也不會刪除集合的元信息叔扼。
    從3.2版本開始事哭,可以使用"deleteOne()""deleteMany()" 方法:

    > db.foo.deleteOne({"y":1})
    { "acknowledged" : true, "deletedCount" : 1 }
    > 
    > db.foo.deleteMany( { "a" : { "$gte" : 11 } } )
    { "acknowledged" : true, "deletedCount" : 2 }
    
  • 清空集合
    刪除文檔通常很快,但如果要清空整個集合瓜富,使用 drop() 更快鳍咱,該函數(shù)不能指定任何限定條件,整個集合都會被刪除与柑,所有元數(shù)據(jù)也都不見了

    > db.foo.drop()
    
  • 更新文檔
    更新操作是不可分割的:若是兩個更新同時發(fā)生谤辜,先到達服務(wù)器的先執(zhí)行,接著執(zhí)行另外一個价捧。所以丑念,兩個需要同時進行的更新會迅速接連完成,比過程不會破壞文檔:最新的更新會取得“勝利”干旧。

    > var dereck = db.people.findOne()
    > dereck
    {
        "_id" : ObjectId("5a4f20eff85ff534e67a4126"),
        "name" : "dereck",
        "age" : 29
    }
    > dereck.age++
    29
    > db.people.update({"_id" : dereck_id}, dereck)
    WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
    > 
    > db.people.find().pretty()
    {
        "_id" : ObjectId("5a4f20eff85ff534e67a4126"),
        "name" : "dereck",
        "age" : 30
    }
    

    從3.2版本開始渠欺,可以使用"updateOne()""updateMany()"
    "replaceOne()" 方法椎眯。

  • 使用修改器
    通常文檔只會有一部分要更新挠将,可以使用原子性的更新修改器,指定對文檔中的某些字段進行更新编整。更新修改器是種特殊的鍵用來指定復雜的更新操作舔稀,比如修改、增加或者刪除鍵掌测,還可能是操作數(shù)組或者內(nèi)嵌文檔内贮。

    • $inc 修改器用來增加(或減少)整型、長整型或雙精度浮點型的值汞斧,如果指定的鍵不存在夜郁,則創(chuàng)建此鍵
    > db.people.find().pretty()
    {
        "_id" : ObjectId("5a4f20eff85ff534e67a4126"),
        "name" : "dereck",
        "age" : 30
    }
    > // 執(zhí)行完以下語句會新建 "score" 鍵
    > db.people.update({"name" : "dereck"}, {"$inc" : {"score" : 100}})
    WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
    > 
    > db.people.find().pretty()
    {
        "_id" : ObjectId("5a4f20eff85ff534e67a4126"),
        "name" : "dereck",
        "age" : 30,
        "score" : 100
    }
    > 
    > // 更改 "score" 鍵的值
    > db.people.update({"name" : "dereck"}, {"$inc" : {"score" : -10}})
    WriteResult({"nMatched":1,"nUpserted":0,"nModified":1})
    > 
    > db.people.find().pretty()
    {
        "_id" : ObjectId("5a4f20eff85ff534e67a4126"),
        "name" : "dereck",
        "age" : 30,
        "score" : 90
    }
    
    • $set 修改器用來指定一個字段的值,如果這個字段不存在粘勒,則創(chuàng)建它竞端。用法與 $inc 類似,不過可以修改任意類型
    > db.people.update({"name" : "dereck"}, { "$set" : { "email" : "dereck@example.com"  }})
    WriteResult({"nMatched":1,"nUpserted":0,"nModified":1})
    > 
    > db.people.find().pretty()
    {
        "_id" : ObjectId("5a4f20eff85ff534e67a4126"),
        "name" : "dereck",
        "age" : 30,
        "score" : 90,
        "email" : "dereck@example.com"
    }
    
    • 如果需要完全刪除某個鍵庙睡,可以使用 $unset 修改器
    • $push 修改器用于向數(shù)組末尾添加一個元素事富,如果數(shù)組不存在,就創(chuàng)建一個新的數(shù)組
    > // 以下語句試圖向 "addresses" 鍵添加一個地址乘陪,此時這個鍵不存在统台,執(zhí)行完后創(chuàng)建該鍵
    > db.people.update({"name" : "dereck"}, { "$push" : { "addresses" : "Road A"  }})
    WriteResult({"nMatched":1,"nUpserted":0,"nModified":1})
    > 
    > db.people.find().pretty()
    {
        "_id" : ObjectId("5a4f20eff85ff534e67a4126"),
        "name" : "dereck",
        "age" : 30,
        "score" : 90,
        "email" : "dereck@example.com",
        "addresses" : [
            "Road A"
        ]
    }
    
    • 使用$each 子操作符,可以通過一次 $push 操作添加多個值
    > db.people.update({"name" : "dereck"}, {"$push" : { "addresses" : { "$each" : ["Road B", "Road C" ]}}})
    
    • $slice$push 組合在一起使用啡邑,可以固定數(shù)組的最大長度贱勃,$slice 的值必須是負數(shù)。比如,下面的代碼確保 addresses 的值最多有5個贵扰,如果數(shù)組的元素沒有超過5個族展,那么所有元素都保留;如果多于5個的話拔鹰,那么只有最后5個元素得以保留仪缸。
    > db.people.update({"name" : "dereck"}, {"$push" : { "addresses" : { "$each" : ["Road D", "Road E",  "Road F"], "$slice" : -5}}})
    WriteResult({"nMatched":1,"nUpserted":0,"nModified":1})
    > 
    > db.people.find().pretty()
    {
        "_id" : ObjectId("5a4f20eff85ff534e67a4126"),
        "name" : "dereck",
        "age" : 30,
        "score" : 90,
        "email" : "dereck@example.com",
        "addresses" : [
            "Road B",
            "Road C",
            "Road D",
            "Road E",
            "Road F"
        ]
    }
    
    • 使用 $addToSet 可以避免向數(shù)組中插入重復值
    > db.people.update({"name" : "dereck"},{"$addToSet" : {"addresses" : { "$each" :["Road F", "Road G"]}}})
    
    • 刪除數(shù)組元素
      若是把數(shù)組看成隊列或者棧,可以用 $pop 修改器列肢,它可以從數(shù)組的任何一端刪除元素恰画。 { "$pop" : { "key" : 1 }} 從數(shù)組末尾刪除一個元素;{ "$pop" : { "key" : -1 }} 則從頭部刪除瓷马。
      當需要通過條件匹配刪除元素時拴还,可以使用 $pull 修改器
    > // 刪除 "Road A"
    > db.people.update({"name" : "dereck"},{"$pull" : {"addresses" : "Road A" }})
    
  • upsert
    upsert (upsert = update + insert)是一種特殊的更新,不是一個操作符欧聘。如果沒有找到符合更新條件的文檔片林,就會以這個條件和更新文檔為基礎(chǔ)創(chuàng)建一個新的文檔;如果找到了匹配的文檔怀骤,則正常更新费封。
    update() 函數(shù)的第 3 個 bool 類型的參數(shù)就表示比更新是否為 upsert。

    > db.people.update({"name" : "sherry"}, {"$set" : {"age" : 20}}, true)
    

    有時需要在創(chuàng)建文檔的同時創(chuàng)建字段并為它賦值蒋伦,但是在之后所有的更新操作中弓摘,這個字段都不再改變代箭,此時可以使用 $setOnInsert硼瓣。比如集合 people 當前有兩條文檔信息,現(xiàn)在插入一條新的文檔:

    > db.people.find().pretty()
    {
        "_id" : ObjectId("5a4f20eff85ff534e67a4126"),
        "name" : "dereck",
        "age" : 30,
        "score" : 90,
        "email" : "dereck@example.com",
        "addresses" : [
            "Road B",
            "Road C",
            "Road D",
            "Road E",
            "Road F",
            "Road G"
        ]
    }
    {
        "_id" : ObjectId("5a4f4af4f60a0bbebb3e3eb2"),
        "name" : "sherry",
        "age" : 28
    }
    > // 以下語句將插入一條文檔牲证,并且設(shè)置字段 birthday 的值
    > db.people.update({ "name" : "duoduo"}, { "$setOnInsert" : { "birthday" : "2015-05-03"}}, true)
    > 
    > db.people.find().pretty()
    {
        "_id" : ObjectId("5a4f20eff85ff534e67a4126"),
        "name" : "dereck",
        "age" : 30,
        "score" : 90,
        "email" : "dereck@example.com",
        "addresses" : [
            "RoadB",
            "Road C",
            "Road D",
            "Road E",
            "Road F",
            "RoadG"
        ]
    }
    {
        "_id" : ObjectId("5a4f4af4f60a0bbebb3e3eb2"),
        "name" : "sherry",
        "age" : 28
    }
    {
        "_id" : ObjectId("5a52d117f60a0bbebb826c34"),
        "name" : "duoduo",
        "birthday" : "2015-05-03"
    }
    

    如果 update 方法執(zhí)行的是 update 操作而不是 insert 操作研叫,那么 $setOnInsert 操作符將無效锤窑,即在執(zhí)行上述語句之前,如果集合中已經(jīng)存在一條 name 為 duoduo 的文檔的話嚷炉,上述 update 語句將不會有任何更新操作

  • 更新多個文檔
    默認情況下渊啰,更新只能對符合匹配條件的第一個文檔執(zhí)行操作,即使有多個文檔符合條件渤昌,只有第一個文檔會被更新虽抄,其他文檔不會發(fā)生變化走搁。要更新所有匹配的文檔独柑,需要將 update 的第 4 個參數(shù)設(shè)置為 true 。

    db.collectionName.update(query, obj, upsert, multi)
    

    update 的 4 個參數(shù)分別表示:

    • query - 需要更新的匹配條件
    • obj - 更新后的新的對象
    • upsert - 布爾類型私植,如果找不到匹配對象忌栅,是否新插入一條文檔,默認是 false
    • multi - 布爾類型,是否更新所有符合匹配條件的對象索绪,默認是 false

7. 索引

不使用索引的查詢稱為全表掃描湖员,對于大集合來說,全表掃描的效率非常低瑞驱。使用了索引的查詢幾乎可以瞬間完成娘摔。然而,使用索引是有代價的:對于添加的每一個索引唤反,每次寫操作(插入凳寺、更新、刪除)都將耗費更多的時間彤侍。

  • 創(chuàng)建索引
    假設(shè)集合 users 有 1000000 條文檔肠缨,文檔結(jié)構(gòu)如下:

    > db.users.findOne()
    {
        "_id" : ObjectId("5a5479c075b6e8450f1ee673"),
        "i" : 1,
        "name" : "user1",
        "age" : 22,
        "created" : ISODate("2018-01-09T08:13:52.193Z")
    }
    

    在字段 name 上創(chuàng)建一個索引:

    > db.users.ensureIndex( { "name" : 1 } )
    

    索引的值是按一定順序排列的,因此盏阶,在使用索引鍵對文檔進行排序時非成罐龋快。然而名斟,只有在首先使用索引鍵進行排序時脑慧,索引才有用。例如砰盐,在下面的排序里漾橙, "name" 上的索引沒什么作用:

    > db.users.find().sort({ "age" : 1, "name" : 1})
    

    這里先根據(jù) "age" 排序再根據(jù) "name" 排序,所以"name"上的索引沒起作用楞卡。

  • 復合索引
    復合索引是一個建立在多個字段上的索引霜运。如果查詢中有多個排序方向或者查詢條件中有多個鍵,這個索引會非常有用蒋腮。比如淘捡,根據(jù)上述排序創(chuàng)建復合索引:

    > db.users.ensureIndex( { "age" : 1, "name" : 1 } )
    
  • 使用覆蓋索引
    當一個索引包含了用戶請求的所有字段,就稱這個索引覆蓋了本次查詢池摧。應該優(yōu)先使用覆蓋索引焦除,為了確保查詢只使用索引就可以完成,應該使用投射({"_id" : 0})來指定不要返回 "_id" 字段(除非它了索引的一部分)作彤。

  • 隱式索引
    如果有一個有 N 個鍵的索引膘魄,那么同時可以“免費”得到所有這 N 個鍵的前綴組成的索引。比如竭讳,有一個如下索引:

    { "a" : 1, "b" : 1, "c" : 1,  ..., "z" : 1 }
    

    那么可以使用如下一系列索引:

    { "a" : 1 }
    { "a" : 1, "b" : 1 }
    { "a" : 1, "b" : 1, "c" : 1}
    ...
    

    這些鍵的任意子集所組成的索引并不一定可用创葡。比如,使用 {"b" : 1} 或者 {"a" : 1, "c" : 1} 作為索引的查詢是不會被優(yōu)化的绢慢。

  • 索引嵌套文檔
    比如對如下文檔中的 "city" 字段創(chuàng)建索引:

    {
        "name" : "dereck",
        "address" : [
            "zone" : "pudong",
            "city" : "shanghai",
            "country" : "China"
        ]
    }
    
    > db.users.ensureIndex( { "address.city" : 1 } )
    
  • 唯一索引
    唯一索引可以確保集合里的每一個文檔的指定鍵都有唯一值灿渴。在創(chuàng)建索引的同時,指定 {"unique" : true}。比如骚露,下面語句確保了集合 users 中的 "username" 是唯一的:

    > db.users.ensureIndex( { "username" : 1, "unique" : true } )
    

    如果一個文檔沒有對應的鍵蹬挤,索引會將其作為 null 存儲。所以棘幸,如果對某個鍵建立了唯一索引焰扳,插入多個缺少改索引鍵的文檔時會失敗。
    索引的大小是有限制的(8 KB)误续,超出限制的條目不會被包含在索引里蓝翰,也不會受到索引的約束。也就是說女嘲,使用索引查詢的時候畜份,返回的結(jié)果可能會漏掉一些文檔;同樣的欣尼,可以插入多個對應鍵超過 8 KB 長的文檔爆雹,因為此時索引限制失效。
    在已有的集合上創(chuàng)建唯一索引時可能會失敗愕鼓,因為集合中已經(jīng)存在重復值了钙态。此時可以在創(chuàng)建索引時同時指定 "dropDups" 選項來去除重復。這個一個粗暴的方式菇晃,當遇到重復的值時册倒,它會保留第一個,之后的重復文檔都會被刪除磺送,無法認為控制驻子。所以,慎用估灿。

    > db.users.ensureIndex( { "username" : 1, "unique" : true,  "dropDups" : true } )
    
  • 稀疏索引
    唯一索引會把 null 看做值崇呵,所以無法將多個缺少唯一索引中的鍵的文檔插入到集合中,如果希望唯一索引只對包含相應鍵的文檔生效馅袁,這時可以將 unique 和 sparse 選項組合在一起來創(chuàng)建稀疏索引域慷。

    > db.users.ensureIndex( {"username":1,"unique":true, "sparse" : true })
    
  • TTL 索引
    “time-to-live index”,具有生命周期的索引汗销。這種索引為每一個文檔設(shè)置一個超時時間犹褒,當文檔到達預設(shè)值的老化程度之后就會被刪除。
    在 ensureIndex 中指定 expireAfterSeconds 選項就可以創(chuàng)建一個 TTL 索引:

    > // 超時時間為 1 小時
    > db.users.ensureIndex({"lastUpdated" : 1}, {"expireAfterSeconds" : 60*60})
    

    MongoDB 每分鐘對 TTL 索引進行一次清理弛针〉铮可以使用 "collMod" 命令修改 "expireAfterSeconds" 的值:

    > db.runCommand({"collMod" : "users",  "index" : {"name" : "lastUpdated_1", "expireAfterSeconds" : 60*60*2}})
    

    一個給定的集合上可以有多個 TTL 索引,但 TTL 索引不能是復合索引钦奋。

  • 索引管理
    查看給定集合上的所有索引信息:

    > db.users.getIndexes()
    

    標識索引座云,創(chuàng)建索引的同時指定索引名稱:

    > db.users.ensureIndex({ "a" : 1, "b" : 1, "c" : 1}, {"name" : "somename"})
    

    刪除索引:

    > db.users.dropIndex( "x_1_y_1" )
    
  • 何時不應該用索引

    提取較小的子數(shù)據(jù)集時,索引非常高效付材。結(jié)果集在原集合中所占的比例越大朦拖,索引的速度就越慢,因為使用索引需要進行兩次查找:一次是查找索引條目厌衔,一次是根據(jù)索引指針去查找相應的文檔璧帝。而全表掃描只需要進行一次查找:查找文檔。

    索引通常適用的情況 全表掃描通常適用的情況
    集合較大 集合較小
    文檔較大 文檔較小
    選擇性查詢 非選擇性查詢

    可以使用 { "$natural" : 1 } 強制 MongoDB 做全表掃描富寿,返回的結(jié)果是按照磁盤上的順序排列的:

    > db.users.find({"name" : "dereck"}).hint({ "$natural" : 1 })
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末睬隶,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子页徐,更是在濱河造成了極大的恐慌苏潜,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件变勇,死亡現(xiàn)場離奇詭異恤左,居然都是意外死亡,警方通過查閱死者的電腦和手機搀绣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門飞袋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人链患,你說我怎么就攤上這事巧鸭。” “怎么了麻捻?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵纲仍,是天一觀的道長。 經(jīng)常有香客問我贸毕,道長巷折,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任崖咨,我火速辦了婚禮锻拘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘击蹲。我一直安慰自己署拟,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布歌豺。 她就那樣靜靜地躺著推穷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪类咧。 梳的紋絲不亂的頭發(fā)上馒铃,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天蟹腾,我揣著相機與錄音,去河邊找鬼区宇。 笑死娃殖,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的议谷。 我是一名探鬼主播炉爆,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼卧晓!你這毒婦竟也來了芬首?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤逼裆,失蹤者是張志新(化名)和其女友劉穎郁稍,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胜宇,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡艺晴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了掸屡。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片封寞。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖仅财,靈堂內(nèi)的尸體忽然破棺而出狈究,到底是詐尸還是另有隱情,我是刑警寧澤盏求,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布抖锥,位于F島的核電站,受9級特大地震影響碎罚,放射性物質(zhì)發(fā)生泄漏磅废。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一荆烈、第九天 我趴在偏房一處隱蔽的房頂上張望拯勉。 院中可真熱鬧,春花似錦憔购、人聲如沸宫峦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽导绷。三九已至,卻和暖如春屎飘,著一層夾襖步出監(jiān)牢的瞬間妥曲,已是汗流浹背贾费。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留檐盟,地道東北人褂萧。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像遵堵,于是被迫代替她去往敵國和親箱玷。 傳聞我的和親對象是個殘疾皇子怨规,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355