學習《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ù)庫中
- admin
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 查詢:"or" 除了可以單個鍵,還可以用來對多個鍵進行 OR 查詢邮丰。
> // "$in" 非常靈活行您,可以指定不同類型的條件和值 > db.users.find({ "user_id" : { "$in" : [ 12345, "dereck" ] })
> // "$or" 接受一個包含所有可能條件的數(shù)組作為參數(shù) > db.users.find({ "$or" : [ {"country" : "China"}, {"city" : "Shanghai"} ] })
當某個查詢既可以使用 "
or" 完成時,優(yōu)先使用 "
in" 只執(zhí)行單次查詢娃循, 而 "
in":[ 12345, "dereck" ]} 需要兩次)后將結(jié)果合并再返回斗蒋,效率較低捌斧。如果不得不使用 "
or" 字句匹配到)。
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" 語句蚓峦。
假設(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" 表達式來運行很泊,在速度上要比常規(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 })