Mongo
設(shè)計應(yīng)用
索引
使用ensureIndex()創(chuàng)建索引
db.users.ensureIndex({
"username:1
})
簡介
通常。在一個特定的集合,不應(yīng)該擁有兩個以上的索引
復(fù)合索引
索引的值是按照一定順序排列的,因此,使用索引鍵對文檔進行排序非???。然而,只有在首先使用索引鍵進行排序時,索引才有用。
復(fù)合索引就是建立在多個字段上的索引
db.users.ensureIndex({
"age": 1,
"username:1
})
通常來說,如果mongodb使用索引進行查詢,那么查詢結(jié)果文檔通常就是按照索引順序排序的
如果對查詢結(jié)果的范圍做了限制,那么mongo在幾次匹配之后就可以不在掃描索引,在這種情況下,將排序鍵放在第一位時一個和好的策略。
可以通過hint來強制使用某個特定的索引
使用復(fù)合索引
在多個鍵上建立的索引就是復(fù)合索引
選擇鍵的方向
索引使用的方向,與排序方向相同即可,注意,相互反轉(zhuǎn)(在每個方向上*-1)的索引時等價的{"age":1,"username":-1}適用的查詢和{"age"-1,"username"1}是完全一樣的
只有基于多個查詢條件進行排序時,索引方向才是你叫重要的,如果只是基于單一索引鍵進行排序
使用覆蓋索引
如果你的查詢只需要查找索引中包含的字段,那就根據(jù)沒必要獲取實際的文檔。當(dāng)一個索引包含用戶請求的所有字段,可以認(rèn)為這個索引覆蓋了本次查詢。在實際中,應(yīng)該使用覆蓋索引,而不是獲取文檔
為了確認(rèn)查詢只使用索引就可以完成,應(yīng)該使用投射來指定不要返回_id字段
如果在覆蓋索引上執(zhí)行explain(),indexOnly字段的值要設(shè)為true
隱式索引
復(fù)合索引具有雙重功能,而且對不同的查詢可以表現(xiàn)出不同的索引。
如果有一個擁有n個鍵的索引,難免你同時得到了所有這n個鍵的前綴組成的索引。
$操作符如何使用索引
低效率的操作符
$where查詢和檢查一個鍵是否存在的查詢完全無法使用索引
$ne查詢可以使用索引,但并不是很有效,因為必須要查看所有索引的條目
$nin就總是要進行全表掃描
范圍
設(shè)計多個字段的索引時,應(yīng)該將會用于精確匹配的字段防到索引的前面,將用于范圍匹配的字段放到最后
索引對象和數(shù)組
mongo允許對嵌套字段和數(shù)組建立索引,嵌套對象和數(shù)組字段可以與符合索引中頂級字段一起使用。
索引嵌套文檔
可以在嵌套文檔的鍵上建立索引,方式和正常的鍵一樣。
例如:
{
"username:"sid",
"loc":{
"ip":"1.2.3.4"
"city":"xxx"
"state":"xxx"
}
}
需要在loc的某一個字段建立索引,以便提高這個字段的查詢速度
db.users.ensureIndex({
"loc.city":1
})
- 對嵌套文檔本身建立索引和對嵌套文檔的某個字段建立索引是不同的
- 對整個文檔建立索引,只會提高整個字段子文檔的查詢速度。只有在進行與子文檔字段順序完全匹配的子文檔查詢(db.users.find({"loc":{"ip":"1.2.3.4","city":"xxx","state":"ny"}})),查詢優(yōu)化器才會使用索引,無法對形如db.users.find({"loc.city":"xxx"})的查詢使用索引
索引數(shù)組
對數(shù)組建立索引,可以高效的搜索數(shù)組中的特定元素
多鍵索引
對于索引的鍵,如果這個鍵在文檔中是一個數(shù)組,那么這個索引就會唄還標(biāo)記為多鍵索引,多鍵索引可能會比非多鍵索引慢一些,可能會友多個索引條目指向同一個文檔,因此在返回結(jié)果時必須要先去除重復(fù)的內(nèi)容
索引基數(shù)
基數(shù)就是集合中某個字段擁有不同值的數(shù)量,一般來說,應(yīng)該在基數(shù)比較高的鍵上建立索引,或者至少應(yīng)該吧基數(shù)高的鍵放在復(fù)合索引的前面
使用explain()和hint()
explain()能夠提供大量的查詢相關(guān)的信息。對于任意查詢,都可以在最后添加一個explain()調(diào)用
字段說明:
-
"cursor": "BtreeCursor age_1_username_1"
BtreeCursor表示使用了索引,使用了{"age":1,"username":1}的索引 -
"isMultiKey":false
用于說明本次查詢是否使用了多鍵索引 -
"n":8332
本次查詢返回的文檔數(shù)量 -
nscannedObjects":8332
按到索引去磁盤上查找實際文檔的次數(shù) -
"nscanned":8332
如果有使用索引,那么這個數(shù)字就是查找過的索引條目數(shù)量,如果本次查詢是一次全表查詢,那么這個數(shù)字就表示檢查過的文檔數(shù)量。 -
"scanAndOrder":false
是否在內(nèi)存中對結(jié)果集進行了排序 -
"indexOnly":false
是否只使用索引就能完成此次查詢 -
"nYields":0
為了讓寫入請求能夠順序執(zhí)行,本次插敘暫停的次數(shù) -
"millis":91
數(shù)據(jù)庫執(zhí)行本次查詢所耗費的毫秒數(shù) -
"indexBounds":{...}
描述了索引的使用情況,給出了索引的遍歷范圍
索引類型
唯一索引
唯一索引可以確保集合的每一個文檔都有唯一值
如果向保證同文檔的“username”鍵都擁有不同的值,那么可以創(chuàng)建一個唯一索引
db.users.ensureIndex(
{
"username":1
},
{
"unique":true
}
)
復(fù)合唯一索引
創(chuàng)建符合唯一索引時,單個鍵的值可以相同,但所有鍵的組合值必須時唯一的
去除重復(fù)
在已有的集合創(chuàng)建唯一索引時可能會失敗,因為集合中肯能已經(jīng)存在重復(fù)值了,通常需要先對已有的數(shù)據(jù)進行處理,在極少數(shù)情況下,可能希望直接刪除重復(fù)的值,創(chuàng)建索引時使用dropDups選項,如果遇到重復(fù)的值,第一個會被保留,之后的重復(fù)文檔都會唄刪除
db.users.ensureIndex(
{
"username":1
},
{
"unique":true,
"dropDups";true
}
)
索引管理
所有的數(shù)據(jù)庫索引信息都存儲在system.indexes集合中,這個是一個保留集合,不能在其中插入或者刪除文檔,直蹦通過ensureIndex或者dropIndexes對其進行操作
創(chuàng)建一個索引之后,可以執(zhí)行db.collectionName.getIndexes()查詢給定集合上的所有索引信息
特殊的索引和集合
固定集合
mongo中普通的集合是動態(tài)的,可以自動增長,但是固定集合,固定集合需要事先創(chuàng)建好,而卻他的大小時固定的。固定集合的行為類似于循環(huán)隊列,如果已經(jīng)滿了,最老的文檔會被刪除,新插入的文檔會占據(jù)這塊空間
創(chuàng)建固定集合
不同于普通集合,固定集合必須在使用前顯示創(chuàng)建,可以使用create命令創(chuàng)建固定集合,使用createCollection函數(shù)
創(chuàng)建一個名為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)建固定集合還可以將已有的某個常規(guī)集合轉(zhuǎn)換成固定集合,使用convertToCapped命令
db.runCommand("convertToCapped","test","size":10000)
自然排序
對于固定排序,自然排序就是文檔從舊到新排序的,當(dāng)然也可以按照從新到舊的順序排序
db.my_collection.find().sort({
"$natural":-1
})
TTL索引
允許為每一個文檔設(shè)置一個超市時間,一個文檔到達預(yù)設(shè)置的老化程度之后就會唄刪除
在ensureIndex中指定expireAlterSecs選項就可以創(chuàng)建一個TTL索引
db.foo.ensureIndex(
{
"lastUpdate":1
},
{
"expireAlterSecs":60*60*24
}
)
在lastUpdate字段上建立了一個ttl索引,如果一個文檔的lastUpdate字段存在并且它的值時日期類型,當(dāng)服務(wù)器時間比文檔的lastUpdate字段的時間晚expireAlterSecs秒時,文檔就會唄刪除
mongo每分鐘對ttl索引進行一次清理,所以不應(yīng)該依賴以秒為單位保證索引的存活狀態(tài)
地理空間索引
mongo支持幾種類型的地理空間索引,其中常用的時2dsphere索引和2d索引
地理空間查詢的類型
可以使用多種不同類型的地理空間查詢:交集、包含、以及接近。查詢時,需要將希望查找的內(nèi)容制定為形如{"$geometry":geoJsonDesc}的GeoJson對象
例如:可以使用$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"查詢完全包含在某個區(qū)域的文檔
db.open.street.map.find({
"loc":{
"$within":{
"$geometry":eastVillage
}
}
})
使用”$near“查詢附近的位置
db.open.street.map.find({
"loc":{
"$near":{
"$geometry":eastVillage
}
}
})
$near是唯一一個會對查詢結(jié)果進行自動排序的地理空間操作符,返回結(jié)果時按照距離由近及遠(yuǎn)排序的
使用GridFS存儲文件
shell下使用mongofiles 命令即可
聚合
聚合框架
對聚合框架可以對集合中的文檔進行變化和組合,可以用多個構(gòu)件創(chuàng)建一個管道,用于對一連串的文檔進行處理,包括篩選、投射、分組、排序、限制、跳過
將一系列操作分別傳給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é)果集會以{"_id":id,"filename":xxx}形式表示
- $group:指定需要進行分組的字段,是由“_id”:"$author"指定的,第二個字段為分組的每個文檔的“count”字段+1,(新加入的文檔中并不會有"count"字段,這是"$group"創(chuàng)建的一個新字段),執(zhí)行后文檔結(jié)構(gòu)為{"_id":"auhthorName","count":articleCount}
- $sort:對文檔中的"count"字段進行降序排序
- $limit:限制最終返回結(jié)果為當(dāng)前結(jié)果中的5個文檔
管道操作符
$match
用于對文檔集合進行篩選,之后就可以在篩選得到的文檔子集做聚合
- 不能在$match中使用地理空間操作符
- 盡可能將$match放在管道的前面位置
$project
可以從文檔中提取字段,可以重命名字段
只包含一個author字段
db.articles.aggregate({
"$project":{
"author":1,
"_id":0
}
})
將投射過的字段進行重命名,將"_id"在返回結(jié)果中重命名為"userId"
db.users.aggregate(
{
"$project":{
"userId":"$_id",
"_id":0
}
}
)
- "$fidldname"會引用fieldname字段的值
- "$tag.3"會被替換為tags數(shù)組中的第4個元素
- 必須顯式將“_id”排除,否在這個字段的值將會返回兩次
數(shù)學(xué)表達式
算術(shù)表達式可用于操作數(shù)值,指定一組數(shù)值,就可以使用這個表達式進行操作了
將”salary“和”bonus“字段的值相加
db.employees.aggregate(
{
"$project":{
"todayPay:{
"$add":["$salary","$bonus"]
}
}
}
)
操作符的語法:
-
"$add":[expr1[,expr2,...,exprN]]
接受一個或多個表達式作為參數(shù),將這些表達式相加 -
"$subtract":[expr1,expr2]
接受兩個表達式作為參數(shù),用第一個表達式減去第二個表達式作為結(jié)果 -
"$multiply":[expr1[,expr2,...,exprN]]
接受一個或者多個表達式,并且將它們相乘 -
”$divide“:[expr1,expr2]
接受兩個表達式,用第一個表達式除以第二個表達式的商作為結(jié)果 -
"$mod":[expr1,expr2]
接受兩個表達式,將第一個表達式除以第二個表達式得到的余數(shù)作為結(jié)果
日期表達式
- $year
- $month
- $week
- $dayOfMonth
- $dayOfWeek
- $dayOfYear
- $hour
- $minute
- $second
字符串表達式
-
"$substr":[expr,startOffset,numToReturn]
第一個參數(shù)expr必須是個字符串,截取這個字符串的子串(從startOffset字節(jié)開始的numToReturn字節(jié)) -
"$concat":[expr1,expr2,...,exprN]
將給定的表達式(或者字符串)連接在一起作為返回結(jié)果 -
"$toLower":expr
參數(shù)expr必須是個字符串值,返回expr的小寫形式 -
”$toUpper:expr
參數(shù)expr必須是個字符串值,返回expr的大寫形式
邏輯表達式
-
"$cmp":[expr1,expr2]
比較expr1和expr2的大小,如果expr1小于expr2,返回負(fù)數(shù),反之返回正數(shù) -
"$strcasecmp":[string1,string2]
比較string1和string2,區(qū)分大小寫,只對羅馬字符組成的字符串有效 -
"$eq"/”$ne“/"$gt"/"gte"/"$lt"/"$lte":[expr1,expr2]
你叫expr1和expr2的大小,返回true或者false
布爾表達式
-
"$and":[expr,[,expr2,...,exprN]]
所有表達式的值都是true,那就返回true,否則返回false -
"$or":[expr,[,expr2,...,exprN]]
只要有任意表達式的值為true,返回true,否賊返回false -
"$not":expr
對expr取反
控制語句
-
"$cond":[booleanExpr,trueExpr,falseExpr]
如果booleanExpr的值為true,那就返回trueExpr,否則返回falseExpr -
"$isNull":[expr,replacementExpr]
如果expr是null,返回replacementExpr,否則返回expr
$group
將文檔依據(jù)特定字段的不同值進行分組
算術(shù)操作符
“$sum”: value
對于分組中的每一個文檔,將value與結(jié)果相加“$avg”: value
返回每個分組的平均值
極值操作符
“$max”: expr
返回分組內(nèi)的最大值“$min”: expr
返回分組內(nèi)的最小值“$first": expr
返回分組的第一個值“$last": expr
返回分組的最后一個值
數(shù)組操作符
“$addToSet”: expr
如果當(dāng)前數(shù)組中不包含expr,那就將它添加到數(shù)組中,在反結(jié)果集中,每個元素最多只出現(xiàn)一次,而且元素的順序時不確定的“$push”: expr
不管expr時什么值,都將它添加到數(shù)組只能怪,返回包含所有值的數(shù)組
$unwind
拆分可以將數(shù)組中的每一個值拆分為單獨的文檔
如果希望在查詢中得到特定的子文檔,先使用“$unwind”得到所有子文檔,再使用“$match”得到想要的文檔
$sort
根據(jù)任何字段或多個字段進行排序
$limit
接受一個數(shù)字n,返回結(jié)果集中的前n個文檔
$skip
接受一個數(shù)字m,丟棄結(jié)果集中的錢n個文檔
MapReduce
找出集合中的所有鍵
map函數(shù)使用特定的emit函數(shù)返回要處理的值,emit會給mapreduce一個鍵和一個值
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é)果的集合名,臨時集合- "timeMollis":12
操作花費的時間,單位時毫秒- “counts”:{...}
用于調(diào)試,包含三個鍵- "input":6
發(fā)送到map函數(shù)的文檔個數(shù)- "emit":14
在map函數(shù)中emit調(diào)用的次數(shù)- "output":5
結(jié)果集合中的文檔數(shù)量
聚合命令
count
返回集合中文檔的數(shù)量
db.foo.count({"x";2})
distinct
用來找出給定鍵的所有不同值,使用時必須指定集合和鍵
db.runCommand(
{
"distinct":"people",
"key":"age"
}
)
group
選定分組所依據(jù)的鍵進行分組,然后對分組內(nèi)的文檔進行聚合得到結(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":指定要進行分組的集合
- "key":"day":指定文檔分組依據(jù)的鍵
- "initial":{"time":0}:每一組reduce函數(shù)調(diào)用中的初始time值,會作為初始文檔傳遞給后續(xù)過程。每一組的所有成員都會使用這個累加器,所以它的任何變化都可以保存下來
- "reduce":function(doc,prev){}:在集合內(nèi)的每個文檔上執(zhí)行,系統(tǒng)會傳遞兩個參數(shù),當(dāng)前文檔和累加器文檔。
使用完成器
完成器用于精簡從數(shù)據(jù)庫傳到用戶的數(shù)據(jù)
將函數(shù)作為鍵使用
分組所依據(jù) 的條件非常復(fù)雜,需要定義一個函數(shù)來決定文檔分組所依據(jù)的鍵
定義分組函數(shù)就要用到$keyf鍵,使用$keyf的group命令
db.posts.group(
{
"ns":"posts",
"$keyf":function(x){
return x.category.toLowerCase();
},
"initializer":...
}
)
應(yīng)用程序設(shè)計
范式化與反范式化
決定何時采用范式化何時采用反范式化需要根據(jù)自己的應(yīng)用程序的實際情況仔細(xì)權(quán)衡
一般來說,數(shù)據(jù)生成越頻繁,就越不應(yīng)該將這些數(shù)據(jù)內(nèi)嵌到其他文檔中
如果內(nèi)嵌字段或者內(nèi)嵌字段數(shù)量時無限增長的,那么應(yīng)該將這些內(nèi)容保存在單獨的集合中,使用引用的方式進行訪問
如果某些字段時文檔數(shù)據(jù)的一部分,那么需要將這些字段內(nèi)嵌到文檔中
如果在查詢文檔時經(jīng)常需要將需要將某個字段排除出去,那么這個字段應(yīng)該放在另外的集合中
內(nèi)嵌數(shù)據(jù)與引用數(shù)據(jù)的比較:
| 更適合內(nèi)嵌 | 更適合引用 |
| -------- | ----- | ---- |
| 子文檔較小| 子文檔較大|
| 數(shù)據(jù)不會定期改變 | 數(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的場景
- 不支持事務(wù)
- 在多個不同維度上對不同類型的數(shù)據(jù)進行連接
注:
- 上述測試在MongoDB 3.4.3-8-g05b19c6中成功
- 上述文字皆為個人看法,如有錯誤或建議請及時聯(lián)系我