Mongo
設(shè)計(jì)應(yīng)用
索引
使用ensureIndex()創(chuàng)建索引
db.users.ensureIndex({
"username:1
})
簡(jiǎn)介
通常暗甥。在一個(gè)特定的集合框往,不應(yīng)該擁有兩個(gè)以上的索引
復(fù)合索引
索引的值是按照一定順序排列的梗醇,因此知允,使用索引鍵對(duì)文檔進(jìn)行排序非常快叙谨。然而温鸽,只有在首先使用索引鍵進(jìn)行排序時(shí),索引才有用手负。
復(fù)合索引就是建立在多個(gè)字段上的索引
db.users.ensureIndex({
"age": 1,
"username:1
})
通常來說涤垫,如果mongodb使用索引進(jìn)行查詢姑尺,那么查詢結(jié)果文檔通常就是按照索引順序排序的
如果對(duì)查詢結(jié)果的范圍做了限制,那么mongo在幾次匹配之后就可以不在掃描索引蝠猬,在這種情況下股缸,將排序鍵放在第一位時(shí)一個(gè)和好的策略。
可以通過hint來強(qiáng)制使用某個(gè)特定的索引
使用復(fù)合索引
在多個(gè)鍵上建立的索引就是復(fù)合索引
選擇鍵的方向
索引使用的方向吱雏,與排序方向相同即可敦姻,注意,相互反轉(zhuǎn)(在每個(gè)方向上*-1)的索引時(shí)等價(jià)的{"age":1,"username":-1}適用的查詢和{"age"-1,"username"1}是完全一樣的
只有基于多個(gè)查詢條件進(jìn)行排序時(shí)歧杏,索引方向才是你叫重要的型宝,如果只是基于單一索引鍵進(jìn)行排序
使用覆蓋索引
如果你的查詢只需要查找索引中包含的字段,那就根據(jù)沒必要獲取實(shí)際的文檔暖呕。當(dāng)一個(gè)索引包含用戶請(qǐng)求的所有字段挂签,可以認(rèn)為這個(gè)索引覆蓋了本次查詢。在實(shí)際中凯力,應(yīng)該使用覆蓋索引茵瘾,而不是獲取文檔
為了確認(rèn)查詢只使用索引就可以完成,應(yīng)該使用投射來指定不要返回_id字段
如果在覆蓋索引上執(zhí)行explain()咐鹤,indexOnly字段的值要設(shè)為true
隱式索引
復(fù)合索引具有雙重功能拗秘,而且對(duì)不同的查詢可以表現(xiàn)出不同的索引。
如果有一個(gè)擁有n個(gè)鍵的索引祈惶,難免你同時(shí)得到了所有這n個(gè)鍵的前綴組成的索引雕旨。
$操作符如何使用索引
低效率的操作符
$where查詢和檢查一個(gè)鍵是否存在的查詢完全無法使用索引
$ne查詢可以使用索引,但并不是很有效捧请,因?yàn)楸仨氁榭此兴饕臈l目
$nin就總是要進(jìn)行全表掃描
范圍
設(shè)計(jì)多個(gè)字段的索引時(shí)凡涩,應(yīng)該將會(huì)用于精確匹配的字段防到索引的前面,將用于范圍匹配的字段放到最后
索引對(duì)象和數(shù)組
mongo允許對(duì)嵌套字段和數(shù)組建立索引疹蛉,嵌套對(duì)象和數(shù)組字段可以與符合索引中頂級(jí)字段一起使用活箕。
索引嵌套文檔
可以在嵌套文檔的鍵上建立索引,方式和正常的鍵一樣可款。
例如:
{
"username:"sid",
"loc":{
"ip":"1.2.3.4"
"city":"xxx"
"state":"xxx"
}
}
需要在loc的某一個(gè)字段建立索引育韩,以便提高這個(gè)字段的查詢速度
db.users.ensureIndex({
"loc.city":1
})
- 對(duì)嵌套文檔本身建立索引和對(duì)嵌套文檔的某個(gè)字段建立索引是不同的
- 對(duì)整個(gè)文檔建立索引,只會(huì)提高整個(gè)字段子文檔的查詢速度筑舅。只有在進(jìn)行與子文檔字段順序完全匹配的子文檔查詢(db.users.find({"loc":{"ip":"1.2.3.4","city":"xxx","state":"ny"}}))座慰,查詢優(yōu)化器才會(huì)使用索引,無法對(duì)形如db.users.find({"loc.city":"xxx"})的查詢使用索引
索引數(shù)組
對(duì)數(shù)組建立索引翠拣,可以高效的搜索數(shù)組中的特定元素
多鍵索引
對(duì)于索引的鍵版仔,如果這個(gè)鍵在文檔中是一個(gè)數(shù)組,那么這個(gè)索引就會(huì)唄還標(biāo)記為多鍵索引,多鍵索引可能會(huì)比非多鍵索引慢一些蛮粮,可能會(huì)友多個(gè)索引條目指向同一個(gè)文檔益缎,因此在返回結(jié)果時(shí)必須要先去除重復(fù)的內(nèi)容
索引基數(shù)
基數(shù)就是集合中某個(gè)字段擁有不同值的數(shù)量,一般來說然想,應(yīng)該在基數(shù)比較高的鍵上建立索引莺奔,或者至少應(yīng)該吧基數(shù)高的鍵放在復(fù)合索引的前面
使用explain()和hint()
explain()能夠提供大量的查詢相關(guān)的信息。對(duì)于任意查詢变泄,都可以在最后添加一個(gè)explain()調(diào)用
字段說明:
-
"cursor": "BtreeCursor age_1_username_1"
BtreeCursor表示使用了索引令哟,使用了{(lán)"age":1,"username":1}的索引 -
"isMultiKey":false
用于說明本次查詢是否使用了多鍵索引 -
"n":8332
本次查詢返回的文檔數(shù)量 -
nscannedObjects":8332
按到索引去磁盤上查找實(shí)際文檔的次數(shù) -
"nscanned":8332
如果有使用索引,那么這個(gè)數(shù)字就是查找過的索引條目數(shù)量妨蛹,如果本次查詢是一次全表查詢屏富,那么這個(gè)數(shù)字就表示檢查過的文檔數(shù)量。 -
"scanAndOrder":false
是否在內(nèi)存中對(duì)結(jié)果集進(jìn)行了排序 -
"indexOnly":false
是否只使用索引就能完成此次查詢 -
"nYields":0
為了讓寫入請(qǐng)求能夠順序執(zhí)行蛙卤,本次插敘暫停的次數(shù) -
"millis":91
數(shù)據(jù)庫執(zhí)行本次查詢所耗費(fèi)的毫秒數(shù) -
"indexBounds":{...}
描述了索引的使用情況狠半,給出了索引的遍歷范圍
索引類型
唯一索引
唯一索引可以確保集合的每一個(gè)文檔都有唯一值
如果向保證同文檔的“username”鍵都擁有不同的值,那么可以創(chuàng)建一個(gè)唯一索引
db.users.ensureIndex(
{
"username":1
},
{
"unique":true
}
)
復(fù)合唯一索引
創(chuàng)建符合唯一索引時(shí)颤难,單個(gè)鍵的值可以相同神年,但所有鍵的組合值必須時(shí)唯一的
去除重復(fù)
在已有的集合創(chuàng)建唯一索引時(shí)可能會(huì)失敗,因?yàn)榧现锌夏芤呀?jīng)存在重復(fù)值了行嗤,通常需要先對(duì)已有的數(shù)據(jù)進(jìn)行處理已日,在極少數(shù)情況下,可能希望直接刪除重復(fù)的值昂验,創(chuàng)建索引時(shí)使用dropDups選項(xiàng)捂敌,如果遇到重復(fù)的值,第一個(gè)會(huì)被保留既琴,之后的重復(fù)文檔都會(huì)唄刪除
db.users.ensureIndex(
{
"username":1
},
{
"unique":true,
"dropDups"泡嘴;true
}
)
索引管理
所有的數(shù)據(jù)庫索引信息都存儲(chǔ)在system.indexes集合中甫恩,這個(gè)是一個(gè)保留集合,不能在其中插入或者刪除文檔酌予,直蹦通過ensureIndex或者dropIndexes對(duì)其進(jìn)行操作
創(chuàng)建一個(gè)索引之后磺箕,可以執(zhí)行db.collectionName.getIndexes()查詢給定集合上的所有索引信息
特殊的索引和集合
固定集合
mongo中普通的集合是動(dòng)態(tài)的,可以自動(dòng)增長(zhǎng)抛虫,但是固定集合松靡,固定集合需要事先創(chuàng)建好,而卻他的大小時(shí)固定的建椰。固定集合的行為類似于循環(huán)隊(duì)列雕欺,如果已經(jīng)滿了,最老的文檔會(huì)被刪除,新插入的文檔會(huì)占據(jù)這塊空間
創(chuàng)建固定集合
不同于普通集合屠列,固定集合必須在使用前顯示創(chuàng)建啦逆,可以使用create命令創(chuàng)建固定集合,使用createCollection函數(shù)
創(chuàng)建一個(gè)名為my_collection大小為10000字節(jié)的固定集合
db.createCollection("my_collection",{
"capped":true,
"size":10000
})
限制固定集合中的文檔的數(shù)量
db.createCollection("my_collection",{
"capped":true,
"size":10000笛洛,
"max":100
})
創(chuàng)建固定集合還可以將已有的某個(gè)常規(guī)集合轉(zhuǎn)換成固定集合夏志,使用convertToCapped命令
db.runCommand("convertToCapped","test","size":10000)
自然排序
對(duì)于固定排序,自然排序就是文檔從舊到新排序的苛让,當(dāng)然也可以按照從新到舊的順序排序
db.my_collection.find().sort({
"$natural":-1
})
TTL索引
允許為每一個(gè)文檔設(shè)置一個(gè)超市時(shí)間沟蔑,一個(gè)文檔到達(dá)預(yù)設(shè)置的老化程度之后就會(huì)唄刪除
在ensureIndex中指定expireAlterSecs選項(xiàng)就可以創(chuàng)建一個(gè)TTL索引
db.foo.ensureIndex(
{
"lastUpdate":1
},
{
"expireAlterSecs":60*60*24
}
)
在lastUpdate字段上建立了一個(gè)ttl索引,如果一個(gè)文檔的lastUpdate字段存在并且它的值時(shí)日期類型狱杰,當(dāng)服務(wù)器時(shí)間比文檔的lastUpdate字段的時(shí)間晚expireAlterSecs秒時(shí)瘦材,文檔就會(huì)唄刪除
mongo每分鐘對(duì)ttl索引進(jìn)行一次清理,所以不應(yīng)該依賴以秒為單位保證索引的存活狀態(tài)
地理空間索引
mongo支持幾種類型的地理空間索引浦旱,其中常用的時(shí)2dsphere索引和2d索引
地理空間查詢的類型
可以使用多種不同類型的地理空間查詢:交集宇色、包含、以及接近颁湖。查詢時(shí)宣蠕,需要將希望查找的內(nèi)容制定為形如{"$geometry":geoJsonDesc}的GeoJson對(duì)象
例如:可以使用$geoIntersects操作符找出與查詢位置相交的文檔
var eastVillage={
"type":"xxx",
"coordinates":{
[-73.9917900,40.7264100],
[-73.9917900,40.7264100],
[-73.9917900,40.7264100],
}
}
db.open.street.map.find({
"loc":{
"$geoIntersects":{
"$geometry":eastVillage
}
}
})
使用"$within"查詢完全包含在某個(gè)區(qū)域的文檔
db.open.street.map.find({
"loc":{
"$within":{
"$geometry":eastVillage
}
}
})
使用”$near“查詢附近的位置
db.open.street.map.find({
"loc":{
"$near":{
"$geometry":eastVillage
}
}
})
$near是唯一一個(gè)會(huì)對(duì)查詢結(jié)果進(jìn)行自動(dòng)排序的地理空間操作符,返回結(jié)果時(shí)按照距離由近及遠(yuǎn)排序的
使用GridFS存儲(chǔ)文件
shell下使用mongofiles 命令即可
聚合
聚合框架
對(duì)聚合框架可以對(duì)集合中的文檔進(jìn)行變化和組合甥捺,可以用多個(gè)構(gòu)件創(chuàng)建一個(gè)管道抢蚀,用于對(duì)一連串的文檔進(jìn)行處理,包括篩選镰禾、投射皿曲、分組、排序吴侦、限制屋休、跳過
將一系列操作分別傳給aggregate()函數(shù)即可
db.articles.aggregate(
{
"$project":{
"author:1
}
},
{
"$group":{
"_id":"$auhtor",
"count":{
"$sum":1
}
}
},
{
"$sort":{
"count":-1
}
},
{
"$limit":5
}
)
- $project:通過指定"filename",1選擇需要投射的字段备韧,0排序不需要的字段劫樟,執(zhí)行完$project操作,結(jié)果集會(huì)以{"_id":id,"filename":xxx}形式表示
- $group:指定需要進(jìn)行分組的字段织堂,是由“_id”:"$author"指定的叠艳,第二個(gè)字段為分組的每個(gè)文檔的“count”字段+1,(新加入的文檔中并不會(huì)有"count"字段易阳,這是"$group"創(chuàng)建的一個(gè)新字段)附较,執(zhí)行后文檔結(jié)構(gòu)為{"_id":"auhthorName","count":articleCount}
- $sort:對(duì)文檔中的"count"字段進(jìn)行降序排序
- $limit:限制最終返回結(jié)果為當(dāng)前結(jié)果中的5個(gè)文檔
管道操作符
$match
用于對(duì)文檔集合進(jìn)行篩選,之后就可以在篩選得到的文檔子集做聚合
- 不能在$match中使用地理空間操作符
- 盡可能將$match放在管道的前面位置
$project
可以從文檔中提取字段潦俺,可以重命名字段
只包含一個(gè)author字段
db.articles.aggregate({
"$project":{
"author":1,
"_id":0
}
})
將投射過的字段進(jìn)行重命名拒课,將"_id"在返回結(jié)果中重命名為"userId"
db.users.aggregate(
{
"$project":{
"userId":"$_id",
"_id":0
}
}
)
- "$fidldname"會(huì)引用fieldname字段的值
- "$tag.3"會(huì)被替換為tags數(shù)組中的第4個(gè)元素
- 必須顯式將“_id”排除徐勃,否在這個(gè)字段的值將會(huì)返回兩次
數(shù)學(xué)表達(dá)式
算術(shù)表達(dá)式可用于操作數(shù)值,指定一組數(shù)值捕发,就可以使用這個(gè)表達(dá)式進(jìn)行操作了
將”salary“和”bonus“字段的值相加
db.employees.aggregate(
{
"$project":{
"todayPay:{
"$add":["$salary","$bonus"]
}
}
}
)
操作符的語法:
-
"$add":[expr1[,expr2,...,exprN]]
接受一個(gè)或多個(gè)表達(dá)式作為參數(shù)疏旨,將這些表達(dá)式相加 -
"$subtract":[expr1,expr2]
接受兩個(gè)表達(dá)式作為參數(shù),用第一個(gè)表達(dá)式減去第二個(gè)表達(dá)式作為結(jié)果 -
"$multiply":[expr1[,expr2,...,exprN]]
接受一個(gè)或者多個(gè)表達(dá)式扎酷,并且將它們相乘 -
”$divide“:[expr1,expr2]
接受兩個(gè)表達(dá)式檐涝,用第一個(gè)表達(dá)式除以第二個(gè)表達(dá)式的商作為結(jié)果 -
"$mod":[expr1,expr2]
接受兩個(gè)表達(dá)式,將第一個(gè)表達(dá)式除以第二個(gè)表達(dá)式得到的余數(shù)作為結(jié)果
日期表達(dá)式
- $year
- $month
- $week
- $dayOfMonth
- $dayOfWeek
- $dayOfYear
- $hour
- $minute
- $second
字符串表達(dá)式
-
"$substr":[expr,startOffset,numToReturn]
第一個(gè)參數(shù)expr必須是個(gè)字符串法挨,截取這個(gè)字符串的子串(從startOffset字節(jié)開始的numToReturn字節(jié)) -
"$concat":[expr1,expr2,...,exprN]
將給定的表達(dá)式(或者字符串)連接在一起作為返回結(jié)果 -
"$toLower":expr
參數(shù)expr必須是個(gè)字符串值谁榜,返回expr的小寫形式 -
”$toUpper:expr
參數(shù)expr必須是個(gè)字符串值,返回expr的大寫形式
邏輯表達(dá)式
-
"$cmp":[expr1,expr2]
比較expr1和expr2的大小凡纳,如果expr1小于expr2窃植,返回負(fù)數(shù),反之返回正數(shù) -
"$strcasecmp":[string1荐糜,string2]
比較string1和string2巷怜,區(qū)分大小寫,只對(duì)羅馬字符組成的字符串有效 -
"$eq"/”$ne“/"$gt"/"gte"/"$lt"/"$lte":[expr1,expr2]
你叫expr1和expr2的大小暴氏,返回true或者false
布爾表達(dá)式
-
"$and":[expr,[,expr2,...,exprN]]
所有表達(dá)式的值都是true延塑,那就返回true,否則返回false -
"$or":[expr,[,expr2,...,exprN]]
只要有任意表達(dá)式的值為true答渔,返回true关带,否賊返回false -
"$not":expr
對(duì)expr取反
控制語句
-
"$cond":[booleanExpr,trueExpr沼撕,falseExpr]
如果booleanExpr的值為true宋雏,那就返回trueExpr,否則返回falseExpr -
"$isNull":[expr,replacementExpr]
如果expr是null务豺,返回replacementExpr磨总,否則返回expr
$group
將文檔依據(jù)特定字段的不同值進(jìn)行分組
算術(shù)操作符
“$sum”: value
對(duì)于分組中的每一個(gè)文檔,將value與結(jié)果相加“$avg”: value
返回每個(gè)分組的平均值
極值操作符
“$max”: expr
返回分組內(nèi)的最大值“$min”: expr
返回分組內(nèi)的最小值“$first": expr
返回分組的第一個(gè)值“$last": expr
返回分組的最后一個(gè)值
數(shù)組操作符
“$addToSet”: expr
如果當(dāng)前數(shù)組中不包含expr笼沥,那就將它添加到數(shù)組中舍败,在反結(jié)果集中,每個(gè)元素最多只出現(xiàn)一次敬拓,而且元素的順序時(shí)不確定的“$push”: expr
不管expr時(shí)什么值,都將它添加到數(shù)組只能怪裙戏,返回包含所有值的數(shù)組
$unwind
拆分可以將數(shù)組中的每一個(gè)值拆分為單獨(dú)的文檔
如果希望在查詢中得到特定的子文檔乘凸,先使用“$unwind”得到所有子文檔,再使用“$match”得到想要的文檔
$sort
根據(jù)任何字段或多個(gè)字段進(jìn)行排序
$limit
接受一個(gè)數(shù)字n累榜,返回結(jié)果集中的前n個(gè)文檔
$skip
接受一個(gè)數(shù)字m营勤,丟棄結(jié)果集中的錢n個(gè)文檔
MapReduce
找出集合中的所有鍵
map函數(shù)使用特定的emit函數(shù)返回要處理的值灵嫌,emit會(huì)給mapreduce一個(gè)鍵和一個(gè)值
map=function(){
for (var key in this){
emit(key,{
count:1
})
}
}
reduce=function(key,emits){
total=0;
for (var i in emits){
total+=emit[i].count;
}
return {
"count":total
};
}
mr=db.runCommand(
{
"mapreduce":"foo",
"map":map,
"reduce":reduce
}
)
操作相關(guān)元信息
- "reuslt":"tmp.mr.mapreduce_1266787811_1"
存放mapreduce結(jié)果的集合名,臨時(shí)集合- "timeMollis":12
操作花費(fèi)的時(shí)間葛作,單位時(shí)毫秒- “counts”:{...}
用于調(diào)試寿羞,包含三個(gè)鍵- "input":6
發(fā)送到map函數(shù)的文檔個(gè)數(shù)- "emit":14
在map函數(shù)中emit調(diào)用的次數(shù)- "output":5
結(jié)果集合中的文檔數(shù)量
聚合命令
count
返回集合中文檔的數(shù)量
db.foo.count({"x";2})
distinct
用來找出給定鍵的所有不同值,使用時(shí)必須指定集合和鍵
db.runCommand(
{
"distinct":"people",
"key":"age"
}
)
group
選定分組所依據(jù)的鍵進(jìn)行分組赂蠢,然后對(duì)分組內(nèi)的文檔進(jìn)行聚合得到結(jié)果文檔
db.runCommand(
{
"ns":"stocks",
"key":"day",
"inital":{
"time":0
},
"$reduce":funcion(doc,prev){
if (doc.time>prev.time){
prev.price=doc.price;
price.time=doc.time;
}
}
}
)
- "ns":"stock":指定要進(jìn)行分組的集合
- "key":"day":指定文檔分組依據(jù)的鍵
- "initial":{"time":0}:每一組reduce函數(shù)調(diào)用中的初始time值绪穆,會(huì)作為初始文檔傳遞給后續(xù)過程。每一組的所有成員都會(huì)使用這個(gè)累加器虱岂,所以它的任何變化都可以保存下來
- "reduce":function(doc,prev){}:在集合內(nèi)的每個(gè)文檔上執(zhí)行玖院,系統(tǒng)會(huì)傳遞兩個(gè)參數(shù),當(dāng)前文檔和累加器文檔第岖。
使用完成器
完成器用于精簡(jiǎn)從數(shù)據(jù)庫傳到用戶的數(shù)據(jù)
將函數(shù)作為鍵使用
分組所依據(jù) 的條件非常復(fù)雜难菌,需要定義一個(gè)函數(shù)來決定文檔分組所依據(jù)的鍵
定義分組函數(shù)就要用到$keyf鍵,使用$keyf的group命令
db.posts.group(
{
"ns":"posts",
"$keyf":function(x){
return x.category.toLowerCase();
},
"initializer":...
}
)
應(yīng)用程序設(shè)計(jì)
范式化與反范式化
決定何時(shí)采用范式化何時(shí)采用反范式化需要根據(jù)自己的應(yīng)用程序的實(shí)際情況仔細(xì)權(quán)衡
一般來說蔑滓,數(shù)據(jù)生成越頻繁郊酒,就越不應(yīng)該將這些數(shù)據(jù)內(nèi)嵌到其他文檔中
如果內(nèi)嵌字段或者內(nèi)嵌字段數(shù)量時(shí)無限增長(zhǎng)的,那么應(yīng)該將這些內(nèi)容保存在單獨(dú)的集合中键袱,使用引用的方式進(jìn)行訪問
如果某些字段時(shí)文檔數(shù)據(jù)的一部分燎窘,那么需要將這些字段內(nèi)嵌到文檔中
如果在查詢文檔時(shí)經(jīng)常需要將需要將某個(gè)字段排除出去,那么這個(gè)字段應(yīng)該放在另外的集合中
內(nèi)嵌數(shù)據(jù)與引用數(shù)據(jù)的比較:
| 更適合內(nèi)嵌 | 更適合引用 |
| -------- | ----- | ---- |
| 子文檔較小| 子文檔較大|
| 數(shù)據(jù)不會(huì)定期改變 | 數(shù)據(jù)經(jīng)常改變 |
| 最終數(shù)據(jù)一致即可 | 中間階段的數(shù)據(jù)必須一致 |
| 文檔數(shù)據(jù)小幅增加 | 文檔數(shù)據(jù)大幅增加 |
| 數(shù)據(jù)通常需要執(zhí)行二次查詢才能獲得 | 數(shù)據(jù)通常不包含在結(jié)果中 |
| 快速讀取 | 快速寫入 |
優(yōu)化數(shù)據(jù)操作
需要在寫入效率更高的模式與讀取更高的模式之間權(quán)衡
不適合MongoDB的場(chǎng)景
- 不支持事務(wù)
- 在多個(gè)不同維度上對(duì)不同類型的數(shù)據(jù)進(jìn)行連接
注:
- 上述測(cè)試在MongoDB 3.4.3-8-g05b19c6中成功
- 上述文字皆為個(gè)人看法杠纵,如有錯(cuò)誤或建議請(qǐng)及時(shí)聯(lián)系我