本次學(xué)習(xí)使用的是麥子學(xué)院的《mongodb最佳實(shí)踐課程》.侵刪.
安裝數(shù)據(jù)庫(kù)
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 #確保安裝的是最新的版本
echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list #將mangodb添加到源
sudo apt-get update
sudo apt-get install -y mongodb-org
在ubuntu下mongodb默認(rèn)已經(jīng)啟動(dòng)
sudo service mongod stop #停止
sudo service mongod start #啟動(dòng)
打開(kāi)mongodb數(shù)據(jù)庫(kù)使用命令mongo麦轰,如果打開(kāi)弓候,可以看到版本
如果打開(kāi)失敗休玩,則再確認(rèn)下是否已經(jīng)啟動(dòng),使用上述命令重新啟動(dòng)一次即可
創(chuàng)建并選擇一個(gè)庫(kù),我使用的庫(kù)名為test片择,圖個(gè)簡(jiǎn)單
use test #自動(dòng)創(chuàng)建并選擇一個(gè)庫(kù)
退出
exit
配置文件所在
/etc/mongod.conf
插入數(shù)據(jù)
插入數(shù)據(jù)時(shí)mongodb會(huì)自動(dòng)生成一個(gè)唯一的_id字段,其實(shí)這個(gè)_id也可以自行指定潜的。
db.students.indert({_id:"my_id",name:"whaike",age:24,contact:[{city:"深圳"},{phone:["1232354235"]}]})
插入測(cè)試數(shù)據(jù)
db.students.insert({name:"張三",school:{name:"清華大學(xué)",city:"北京"},age:19,gpa:3.97})
db.students.insert({name:"李四",school:{name:"北京大學(xué)",city:"北京"},age:20,gpa:3.3})
db.students.insert({name:"王二",school:{name:"交通大學(xué)",city:"上海"},age:22,gpa:3.68})
db.students.insert({name:"小牛",school:{name:"哈工大",city:"哈爾濱"},age:21,gpa:3.50})
db.students.insert({name:"小馬",school:{name:"交通大學(xué)",city:"西安"},age:21,gpa:3.70})
db.students.insert({name:"小朱"})
查詢(xún)
db.students.find() #查詢(xún)所有記錄,也可以使用db.students.find({})
db.students.find({name:"張三"}) #查詢(xún)name為張三的記錄
db.students.find({"school.name":"交通大學(xué)"}) #查詢(xún)學(xué)校名稱(chēng)為交通大學(xué)的記錄
db.students.find({"school.name":"交通大學(xué)","school.city":"西安"}) #查詢(xún)西安的交通大學(xué)
db.students.find({$or:[{"school.name":"交通大學(xué)"},{"school.city":"北京"}]}) #or運(yùn)算符,條件或字管,使用列表
db.students.find({$and:[{"school.name":"交通大學(xué)"},{"school.city":"上海"}]}) #and運(yùn)算符啰挪,條件與信不,使用列表
db.students.find({age:{$ne:20}}) #ne運(yùn)算符,條件不等于
也可以使用objectid進(jìn)行查詢(xún)
> db.students.find({"_id" : ObjectId("58ad4e19a169ed50558d1209")})
{ "_id" : ObjectId("58ad4e19a169ed50558d1209"), "name" : "王二", "school" : { "name" : "交通大學(xué)", "city" : "上海" }, "age" : 22, "gpa" : 3.68 }
使用正則進(jìn)行匹配
db.students.find({name:/^張/}) #匹配開(kāi)頭
db.students.find({name:/.*四/}) #匹配結(jié)尾
使用exists查詢(xún)某條記錄是否存在亡呵。
db.students.find({school:{$exists:false}}) #查詢(xún)記錄中沒(méi)有school的記錄
更新
通過(guò)find命令查看某條記錄的_id值抽活,這個(gè)值是mongodb自動(dòng)生成的<b>主鍵</b>,官網(wǎng)解釋為
A special 12-byte BSON type that guarantees uniqueness within the collection. The ObjectId is generated based on timestamp, machine ID, process ID, and a process-local incremental counter. MongoDB uses ObjectId values as the default values for _id fields.
12字節(jié)的BSON型數(shù)據(jù),前4字節(jié)表示時(shí)間戳锰什,然后3字節(jié)表示機(jī)器標(biāo)識(shí)碼下硕,然后2字節(jié)由進(jìn)程ID組成 (PID),最后3字節(jié)是隨機(jī)數(shù)。這樣做不僅可以保證其唯一性汁胆,還能保證在高并發(fā)的情況下依然保持唯一性梭姓。
該值其實(shí)是一個(gè)96位二進(jìn)制數(shù)。
使用_id值查找記錄嫩码,結(jié)合set關(guān)鍵字進(jìn)行精確更新操作如下
db.students.update({"_id" : ObjectId("58ad4e19a169ed50558d1207")},{$set:{age:18,gpa:3.95}}) #set關(guān)鍵字誉尖,精確操作
inc運(yùn)算符表示加法
db.students.update({"_id" : ObjectId("58ad4e19a169ed50558d1207")},{$inc:{age:1}}) #將age+1
max關(guān)鍵字可以保證記錄中的值為最大值,如果記錄中的值比它小則更新它谢谦,如果比它大則不更新释牺。min同理,保證最小值萝衩。
db.students.update({"_id" : ObjectId("58ad4e19a169ed50558d1207")},{$max:{age:21}})
mul為翻倍的操作
db.students.update({"_id" : ObjectId("58ad4e19a169ed50558d1207")},{$mul:{age:2}}) #age值做x2操作.
unset刪除記錄,精確刪除
db.students.update({"_id" : ObjectId("58ad4e19a169ed50558d1207")},{$unset:{school:true}})
更新2
假設(shè)想更新所有記錄回挽,將他們的age+1
> db.students.update({},{$inc:{age:1}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
由此可見(jiàn)mongodb默認(rèn)只更新一條記錄
如果要更新多條記錄
> db.students.update({},{$inc:{age:1}},{multi:true})
WriteResult({ "nMatched" : 6, "nUpserted" : 0, "nModified" : 6 })
multi表示找到一條記錄更新之后繼續(xù)尋找下一條進(jìn)行更新
setOnInsert中的內(nèi)容表示只在插入操作的時(shí)候有效们陆,upsert表示存在數(shù)據(jù)就更新沒(méi)有數(shù)據(jù)則插入洁奈。
> db.students.update({name:"Mike"},{$inc:{age:1},$setOnInsert:{school:{name:"new school"}}},{upsert:true})
WriteResult({
"nMatched" : 0,
"nUpserted" : 1,
"nModified" : 0,
"_id" : ObjectId("58ae7693cf9b6da6d48b6c8f")
})
此時(shí)可以看到匹配到0條所以插入1條专酗。
當(dāng)我們?cè)俅螆?zhí)行時(shí)辟癌,其school的記錄已經(jīng)有了坷牛,故不會(huì)再發(fā)生插入操作亲桥,也就是setOnInsert字段后的內(nèi)容無(wú)效轩娶,即使你修改了其中的數(shù)據(jù)魄缚,它只會(huì)進(jìn)行age+1的操作了暗甥,如下
> db.students.update({name:"Mike"},{$inc:{age:1},$setOnInsert:{school:{name:"new school--2"}}},{upsert:true})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
BSON
BSON文檔:mongodb的根本喜滨,與json非常類(lèi)似,json-style document的形式撤防,bson是二進(jìn)制Bin的,而json是文本text的虽风。
mongodb有兩個(gè)特點(diǎn)
1、面向Document.他的增刪改查都是document的形式寄月,每一條記錄都是document.
2辜膝、Key-Value 的數(shù)據(jù)組織形式。
mongodb的優(yōu)勢(shì)在于他可以很容易將數(shù)據(jù)映射成key-value的形式漾肮,多數(shù)情況可以將程序中的object直接與數(shù)據(jù)庫(kù)達(dá)成此映射厂抖,很方便數(shù)據(jù)交換。
注意:
_id
字段為主鍵克懊,他的值覆蓋主鍵忱辅,如果你提供了此值則不會(huì)使用mongodb自動(dòng)提供的值七蜘。- 所有Field不能以
$
開(kāi)頭,這個(gè)符號(hào)被mongodb保留使用了.- name不能使用
*.*
這樣的格式,這個(gè)點(diǎn)好在key中很容易識(shí)別錯(cuò)誤.- key可以重名墙懂,但最好不要崔梗,很容易查詢(xún)到錯(cuò)誤的value.
- value的類(lèi)型比較嚴(yán)格,因?yàn)榇嬖趎umberlong格式垒在,存入的數(shù)據(jù)如果是1則很可能存儲(chǔ)為"1"蒜魄,造成查詢(xún)時(shí)無(wú)法匹配,這個(gè)與使用的框架或語(yǔ)言有關(guān)场躯。
- mongodb對(duì)于單個(gè)的document的限制為16M.
內(nèi)嵌對(duì)象與reference
在MongoDB中谈为,表示關(guān)系有兩種辦法:
一種是嵌套(embedded),既是將一個(gè)文檔包裹一個(gè)子文檔踢关;
另一種是引用鏈接(reference link)伞鲫,使用MongoDB的DBRef對(duì)象建立文檔和文檔之間的關(guān)系。
關(guān)于表示關(guān)系的詳細(xì)說(shuō)明可以參看這篇博文
MongoDB數(shù)據(jù)庫(kù)關(guān)系表示和設(shè)計(jì):(1)嵌套文檔和引用鏈接
索引
查詢(xún)的三個(gè)方式
index seek #直接索引 - 快
index scan #掃索引 - 中
table scan #掃表 - 慢
mongodb使用BTREE實(shí)現(xiàn)索引签舞。
開(kāi)始使用name查詢(xún)記錄
創(chuàng)建索引
db.students.ensureIndex({name:1}) #為name創(chuàng)建索引
db.students.getIndexes()
#查看是否存在索引
此時(shí)根據(jù)name查看數(shù)據(jù)就會(huì)走Btree索引的方式
> db.students.find({name:"Mike"}).explain()
{
"cursor" : "BtreeCursor name_1",
..........
explain()可以查看執(zhí)行某條命令的具體信息秕脓。
創(chuàng)建聯(lián)合索引
db.students.ensureIndex({age:1,gpa:1})
此時(shí)數(shù)據(jù)庫(kù)中的順序會(huì)先按age排序儒搭,然后根據(jù)gpa排序,有點(diǎn)類(lèi)似DataFrame的Index吧.
此時(shí)查詢(xún)建立了索引的字段會(huì)走索引,例如
> db.students.find({age:{$gt:21},gpa:{$lt:3.6}}).explain()
{
"cursor" : "BtreeCursor age_1_gpa_1",
"isMultiKey" : false,
"n" : 1,
"nscannedObjects" : 1,
"nscanned" : 4,
........
單查某一個(gè)比如gpa則不會(huì)走此索引
> db.students.find({gpa:{$lt:3.6}}).explain()
{
"cursor" : "BasicCursor",
"isMultiKey" : false,
"n" : 2,
"nscannedObjects" : 7,
"nscanned" : 7,
......
樹(shù)的表達(dá)和查詢(xún)
假設(shè)某平臺(tái)存在這樣的數(shù)據(jù)結(jié)構(gòu)
設(shè)計(jì)此document可以使用{_id:...,symble:"電腦"}
為保證某字段其唯一性魂仍,可以為其創(chuàng)建一個(gè)索引和一個(gè)唯一索引
db.cats.ensureIndex({symbol:1},{unique:1})
那么如何表示其關(guān)系呢,
可以增加children字段
{_id:...,symble:"電腦",children:["平板","臺(tái)式機(jī)","筆記本"]}
如果覺(jué)得一個(gè)數(shù)組中存儲(chǔ)的數(shù)據(jù)太多,可以使用parent字段
{_id:...,symble:"電腦",children:["電子"]}
這樣查詢(xún)的時(shí)候使用遞歸或者其他方式進(jìn)行循環(huán)查詢(xún)可以得出特定需求的結(jié)果。
當(dāng)然還有更好的方式:
1、Ancestors Array遇伞。在每個(gè)子類(lèi)中使用一個(gè)字段專(zhuān)門(mén)用來(lái)記錄其所有的父輩爺輩等通路路徑上的所有節(jié)點(diǎn)巍耗,再為該字段創(chuàng)建索引,查詢(xún)的時(shí)候直接使用這個(gè)索引即可.
舉例:
{_id:...,symble:"電腦",ancestors:["電子",電腦]}
建立索引:ensureIndex({ancestors:1})
查詢(xún):find({ancestors:"電子"})
這樣就可以查到所有acenstors中帶電子的記錄亲族,而且還是走的索引,效率比較高。
2、Path 。這是官方的做法榛斯。跟上一個(gè)有點(diǎn)類(lèi)似驮俗,但是它使用的是path字段,為string類(lèi)型聋丝,內(nèi)容為嚴(yán)格的路徑節(jié)點(diǎn)順序,并使用指定字符進(jìn)行分隔弱睦,例如"|",查詢(xún)的時(shí)候可以使用正則垒拢,find({$path:/^某節(jié)點(diǎn)名|節(jié)點(diǎn)名/})
舉例:
{_id:...,symble:"電腦",ancestors:"電子|電腦"}
查詢(xún):find({$path:/^電子/}) #查詢(xún)電子下所有記錄
查詢(xún):find({$path:/^電子|電腦/}) #查詢(xún)電子-電腦下所有記錄
3、Model Tree Structures
詳見(jiàn)官方文檔Model Tree Structures
查詢(xún)拓展
find({條件},{映射(也叫投影技術(shù))})
find({},{字段名:0,name:1,age:1}) #0表示不需要求类,1表示需要
分頁(yè)技術(shù)
db.students.find({},{"_id":0,name:1,age:1}).limit(2).skip(2).sort({age:1}) #組合使用limit,skip,sort
limit表示每一頁(yè)多少條數(shù)據(jù),skip表示跳過(guò)多少條數(shù)據(jù),如果limit和skip一起使用尸疆,則跳過(guò)指定數(shù)據(jù)后會(huì)顯示之后limit條數(shù)據(jù).
排序中要使用字典方式,value的1表示順序-1表示倒序(排序的sort函數(shù)放在任何位置效果都是一樣的!這點(diǎn)與其他數(shù)據(jù)庫(kù)不一樣),倒序的排序也會(huì)優(yōu)先使用索引脖捻,而且是在順序的索引基礎(chǔ)上進(jìn)行reverse嗜浮。
數(shù)組內(nèi)嵌對(duì)象的查詢(xún)
簡(jiǎn)單查詢(xún)
插入數(shù)據(jù)
db.students.insert({name:"艾客",school:{name:"地球大學(xué)",city:"深圳"},courses:[{name:"MongoDB",grade:88},{name:"Java",grade:99}],age:19,gpa:3.9})
查詢(xún)
db.students.find({courses:{name:"MongoDB",grade:88}}) #精確查詢(xún)
db.students.find({"courses.name":"MongoDB"}) #模糊查詢(xún)所有匹配
db.students.find({"courses.0.name":"MongoDB"}) #數(shù)組中第n個(gè)元素(對(duì)象)的name為mongodb的
db.students.find({courses:{$size:2}}) #查詢(xún)ourses中恰好兩個(gè)元素的數(shù)據(jù)
復(fù)合查詢(xún)
插入數(shù)據(jù)
db.students.insert({name:"啊哈",school:{name:"宇宙大學(xué)",city:"深圳"},courses:[{name:"MongoDB",grade:88,quiz:[9,7,9,10]},{name:"Java",grade:98,quiz:[5,6,7,9]}],age:24,gpa:3.8})
查詢(xún)
db.students.find({courses:{$elemMatch:{name:"MongoDB",grade:{$gte:88}}}}) #復(fù)合查詢(xún)
db.students.find({courses:{$elemMatch:{name:"MongoDB",quiz:{$elemMatch:{$gt:3,$lt:5}}}}}) #復(fù)合查詢(xún)elemMatch
db.students.find({courses:{$elemMatch:{name:"MongoDB",quiz:{$all:[8,9]}}}}) #all匹配數(shù)組中的元素
更新插入
添加記錄
db.students.update({"_id" : ObjectId("58a903fbce4e9c33a3563dc3")},{$addToSet:{courses:{name:"Swift",grade:89}}})
addToSet
可以保證數(shù)據(jù)的唯一性,如果存在記錄摩疑,則不會(huì)添加此字段,如果想要添加同樣的記錄兩條危融,則要使用push,從已有的元素中刪除使用pull(嚴(yán)格匹配,也就是值也要正確)雷袋,pull該條數(shù)據(jù)中所有符合條件的全部刪除吉殃,如果只刪除相同數(shù)據(jù)的其中一條則需要更精確的匹配<b>(這里需要再驗(yàn)證下)</b>。
2.4之前如果要添加或者刪除多條的時(shí)候使用pushAll,pullAll
版本2.4之后楷怒,push也可以插入多條
$push:{courses:{$each:[{name:"Swift",grade:89},{name:"C++",grade:87}],$sort:{grade:-1}}}
上面的代碼在使用each之后蛋勺,此時(shí)的push可以使用數(shù)組進(jìn)行多個(gè)元素的添加,而且支持排序.
$push:{courses:{$each:[],$sort:{grade:1}}} #使用空的方括號(hào)鸠删,可以單獨(dú)進(jìn)行排序操作
$position:2 表示插入的位置抱完,從0開(kāi)始計(jì)算
$slice:2 切割,只顯示/保留數(shù)據(jù)數(shù)組中的前多少數(shù)據(jù)
以上三個(gè)sort/position/slice都是與each搭配使用的!
更精確的修改操作
db.students.update({"_id":ObjectId("......"),"courses.name":"Swift"},{$set:{"courses.$.grade":99}})
更精確的修改操作.courses.$.grade中使用$符號(hào)表示不知道查到的元素在該條數(shù)據(jù)中的位置,如果知道刃泡,可以使用數(shù)字巧娱,從0開(kāi)始。
db.students.update({"_id" : ObjectId("58a903fbce4e9c33a3563dc3"),"courses.name":"MongoDB"},{$push:{"courses.$.quiz":9.6}}) #為某條數(shù)據(jù)中某數(shù)組中的數(shù)組增加一個(gè)值
游標(biāo)cursor
var cursor = db.students.find({})
cursor.limit(2)#取出多少條記錄
cursor.count()#總共多少條記錄
cursor.objsLeftInBatch()查詢(xún)?nèi)〕鲆徊糠钟涗浿筮€剩下多少條數(shù)據(jù)
cursor.hasNext() #返回布爾值烘贴,是否還存在記錄禁添,也就是是否還可以執(zhí)行next()方法取得數(shù)據(jù)
cursor.next() #取出下一條數(shù)據(jù)
cursor.toArray() #將數(shù)據(jù)轉(zhuǎn)化為數(shù)組,可以通過(guò)[n]訪(fǎng)問(wèn)某一條記錄
db.students.find({}).map(function(doc){return doc.name}) #類(lèi)似js
db.students.find({}).map(function(doc){return {"name":doc.name,"gpa":doc.gpa/4*100}}) #可以寫(xiě)復(fù)雜點(diǎn)的函數(shù)
db.students.find().forEach(function(doc){print(doc.name)}) #forEach函數(shù)與map函數(shù)類(lèi)似也可以將參數(shù)逐個(gè)傳入進(jìn)行處理
db.students.find({},{"_id":0,age:1,gpa:1},hint({age:1,gpa:1})) #投影+排序 hint使用指定索引.強(qiáng)迫使用復(fù)合索引
地理位置查詢(xún)
先插入數(shù)據(jù)
db.pois.insert({name:"AAA Store",loc:{type:"Point",coordinates:[70,30]}})
db.pois.insert({name:"BBB Bank",loc:{type:"Point",coordinates:[69.99,30.01]}})
db.pois.insert({name:"CCC Park",loc:{type:"Polygon",coordinates:[[[70,30],[71,31],[71,30],[70,30]]]}})
db.pois.insert({name:"DDD Forest",loc:{type:"Polygon",coordinates:[[[65,68],[67,68],[67,69],[65,68]]]}})
為地理位置創(chuàng)建索引
db.pois.ensureIndex({loc:"2dsphere"})
結(jié)果自動(dòng)按照由近到遠(yuǎn)的順序
$maxDistance:1000000單位是米
如果不創(chuàng)建索引則查詢(xún)會(huì)報(bào)錯(cuò)code:17007
附近地點(diǎn)查詢(xún)
db.pois.find({loc:{
$near:{
$geometry:{
type:"Point",
coordinates:[70,30]
},
$maxDistance:1000000
}
}})
區(qū)域的點(diǎn)組成的環(huán)需要封閉,也就是第一個(gè)點(diǎn)的坐標(biāo)跟最后一個(gè)點(diǎn)的坐標(biāo)需要相同,如下
區(qū)域內(nèi)地點(diǎn)查詢(xún)
db.pois.find({loc:{
$geoWithin:{
$geometry:{
type:"Polygon",
coordinates:[[[70,30],[71,31],[71,30],[70,30]]]
}
}
}})
使用geoNear command
runCommand對(duì)數(shù)據(jù)庫(kù)執(zhí)行一些命令
查詢(xún)
db.runCommand(
{
geoNear:"pois",
near:{type:"Point",coordinates:[70,30]},
spherical:true,
minDistance:3000,
maxDistance:7000
}
)
得到結(jié)果
{
"results" : [ ],
"stats" : {
"nscanned" : NumberLong(13),
"objectsLoaded" : NumberLong(4),
"avgDistance" : NaN,
"maxDistance" : 0,
"time" : 3
},
"ok" : 1
}
在查詢(xún)的結(jié)果集中
results以數(shù)組的形式保存查詢(xún)到的結(jié)果桨踪,數(shù)組中的每個(gè)元素都是一個(gè)objects老翘,擁有兩個(gè)key:
dis:距離,離當(dāng)前位置的距離
obj:查詢(xún)到的具體數(shù)據(jù)對(duì)象
狀態(tài)stats顯示統(tǒng)計(jì)數(shù)據(jù)
avgDistance:平均距離
maxDistance:最大距離
修改范圍使其可以查詢(xún)到數(shù)據(jù),驗(yàn)證results是否準(zhǔn)確酪捡,以下:
db.runCommand(
{
geoNear:"pois",
near:{type:"Point",coordinates:[70,30]},
spherical:true,
minDistance:0,
maxDistance:700000
}
)
關(guān)于2dsphere索引叁征,詳見(jiàn)官網(wǎng)2dsphere Indexes
全文索引
首先插入數(shù)據(jù)
db.text.insert({content:"text performs a text search on the content of the fields indexed with a text index"})
db.text.insert({content:"When dealing with a small number of documents,.it is possible for the full-text-s"})
db.text.insert({content:"Soros enjoys playing mongo"})
db.text.insert({content:"Why don't you use mongo-db?"})
text創(chuàng)建全文索引
db.text.ensureIndex({content:"text"})
查詢(xún)
db.text.find({$text:{$search:"mongo"}})
也可以為search指定language
db.text.find({$text:{$search:"mongo",$language:"none"}}) #不區(qū)分語(yǔ)言
db.text.find({$text:{$search:"mongo",$language:"en"}}) #英文
數(shù)據(jù)庫(kù)管理初步
一般數(shù)據(jù)庫(kù)在運(yùn)行的時(shí)候只有一個(gè)服務(wù),那么如果它除了問(wèn)題停止運(yùn)行了怎么辦呢逛薇?
此時(shí)我們可以運(yùn)行兩個(gè)服務(wù)捺疼,當(dāng)?shù)谝粋€(gè)服務(wù)關(guān)閉的時(shí)候自動(dòng)切換到第二個(gè)服務(wù),這樣就可以保證數(shù)據(jù)庫(kù)的正常訪(fǎng)問(wèn)了永罚。
首先查看mongodb的進(jìn)程ps -ax | grep mongod
為了提高服務(wù)的高可用性,增加一個(gè)服務(wù)
sudo mongod --dbpath /srv/data --port 12333 #在前臺(tái)啟動(dòng)一個(gè)
此時(shí)我的ubuntu報(bào)錯(cuò)
ERROR: dbpath (/srv/data) does not exist.
Create this directory or give existing directory in --dbpath.
See http://dochub.mongodb.org/core/startingandstoppingmongo
于是新建這個(gè)目錄sudo mkdir /srv/data
,再次執(zhí)行時(shí)
顯示<code>waiting for connections on port 12333</code>時(shí)則表示成功了
可以使用ctrl+c結(jié)束啤呼,且不必?fù)?dān)心會(huì)有其他殘留問(wèn)題,這點(diǎn)可以從它自動(dòng)打印出來(lái)的信息得到.
前臺(tái)啟動(dòng)的方式總是不方便的呢袱,那么如何后臺(tái)啟動(dòng)呢
sudo mongod --fork --logpath /var/log/mongodb2.log --dbpath /srv/data --port 12333 #在后臺(tái)啟動(dòng)一個(gè)服務(wù)
可以看到啟動(dòng)成功的信息
about to fork child process, waiting until server is ready for connections.
forked process: 2596
child process started successfully, parent exiting
連接其他進(jìn)程的mongo數(shù)據(jù)庫(kù)
mongo --port 12333
在數(shù)據(jù)庫(kù)內(nèi)
db.serverCmdLineOpts()
可以查看連上的數(shù)據(jù)庫(kù)的詳細(xì)信息
一般使用后臺(tái)的方式如何停止呢
方式一
使用進(jìn)程ID進(jìn)行關(guān)閉
sudo kill (process id) #一般關(guān)閉數(shù)據(jù)庫(kù)
方式二
可以在數(shù)據(jù)庫(kù)內(nèi)關(guān)掉它
<small>admin用于管理當(dāng)前數(shù)據(jù)庫(kù)</small>
use admin
db.shutdownServer() #關(guān)閉當(dāng)前數(shù)據(jù)庫(kù)
方式三
使用dbpath在shell中指定關(guān)閉某個(gè)數(shù)據(jù)庫(kù)
sudo mongod --shutdown --dbpath /srv/data #使用dbpath指定關(guān)閉哪個(gè)數(shù)據(jù)庫(kù)
數(shù)據(jù)庫(kù)服務(wù)的監(jiān)控和profiling
db.runCommand({serverStatus:1}) #查看服務(wù)的詳細(xì)信息
db.runCommand({serverStatus:1}).pid #查看pid
db.runCommand({serverStatus:1}).mem #查看內(nèi)存
> db.runCommand({serverStatus:1}).mem
{
"bits" : 64,
"resident" : 64, #在RAM中所占用的空間
"virtual" : 662, #虛擬內(nèi)存
"supported" : true,
"mapped" : 240, #所有存儲(chǔ)空間
"mappedWithJournal" : 480
}
mongodb使用內(nèi)存映射文件,映射到虛擬內(nèi)存官扣,然后像讀寫(xiě)內(nèi)存一樣來(lái)讀寫(xiě)文件,類(lèi)似操作系統(tǒng)的交換內(nèi)存方式.
> db.runCommand({serverStatus:1}).extra_info
{
"note" : "fields vary by platform",
"heap_usage_bytes" : 62586272,
"page_faults" : 66 #這個(gè)值越高讀寫(xiě)效率越低
}
使用sharding減少數(shù)據(jù)量的方式或增加內(nèi)存的方式來(lái)降低page_faults值.
查看數(shù)據(jù)進(jìn)出量等
> db.runCommand({serverStatus:1}).network
{ "bytesIn" : 6160, "bytesOut" : 28570, "numRequests" : 69 }
如果numRequests過(guò)高可以考慮優(yōu)化一下document或者采用集群方案
> db.runCommand({serverStatus:1}).connections
{ "current" : 1, "available" : 51199, "totalCreated" : NumberLong(3) }
多少個(gè)客戶(hù)端正在鏈接數(shù)據(jù)庫(kù),總共允許多少個(gè),總共創(chuàng)建了多少個(gè)用戶(hù)
mongodb可以通過(guò)profile來(lái)監(jiān)控?cái)?shù)據(jù)羞福,進(jìn)行優(yōu)化惕蹄。
db.runCommand({dbStats:1})#數(shù)據(jù)庫(kù)的統(tǒng)計(jì)
打開(kāi)profile
> db.runCommand({profile:-1}) #記錄所有操作的時(shí)間
{ "was" : 0, "slowms" : 100, "ok" : 1 }
> db.runCommand({profile:1}) #只記錄比slowms慢的查詢(xún)
{ "was" : 0, "slowms" : 100, "ok" : 1 }
db.runCommand({profile:0}) #關(guān)閉profile
db.runCommand({profile:2}) #打開(kāi)慢查詢(xún)
db.system.profile.find({millis:{$gt:1}},{millis:1,op:1,query:1,ns:1}) #查看大于1ms的操作,后面{}中表示投影出來(lái)哪些數(shù)據(jù)
分布式屬性Replication
一組Mongodb復(fù)制集,就是一組mongod進(jìn)程治专,這些進(jìn)程維護(hù)同一個(gè)數(shù)據(jù)集合卖陵。復(fù)制集提供了數(shù)據(jù)冗余和高等級(jí)的可靠性。保證數(shù)據(jù)在生產(chǎn)部署時(shí)的冗余和可靠性张峰,通過(guò)在不同的機(jī)器上保存副本來(lái)保證數(shù)據(jù)的不會(huì)因?yàn)閱吸c(diǎn)損壞而丟失泪蔫。能夠隨時(shí)應(yīng)對(duì)數(shù)據(jù)丟失、機(jī)器損壞帶來(lái)的風(fēng)險(xiǎn)喘批。
Replica Set (cluster)
A replica set in MongoDB is a group of mongod processes that maintain the same data set.
其中撩荣,一個(gè)primary,多個(gè)secondary饶深,寫(xiě)只能在primary上
盜來(lái)一圖
一組集群只有一個(gè)主導(dǎo)餐曹,他接收所有客戶(hù)端的操作,其他的Secondary獲得主服務(wù)器primary的數(shù)據(jù)并保持同步,主服務(wù)掛掉之后他們會(huì)通過(guò)選舉的方式定哪個(gè)secondary升級(jí)為primary以保證數(shù)據(jù)庫(kù)的正常運(yùn)行.
配置集群
whaike是為replication指定的一個(gè)名字,可以隨意起名
sudo mongod --fork --logpath /var/log/mongodb2.log --dbpath /srv/data --port 12333 --replSet "whaike"
sudo mongod --fork --logpath /var/log/mongodb3.log --dbpath /srv/data1 --port 12334 --replSet "whaike"
sudo mongod --fork --logpath /var/log/mongodb4.log --dbpath /srv/data2 --port 12335 --replSet "whaike"
啟動(dòng)了三個(gè)
連接其中一個(gè)mongo --port 12333
鍵入rs.initiate()
查看狀態(tài)rs.status()
> rs.status()
{
"set" : "whaike",
"date" : ISODate("2017-02-24T08:36:51Z"),
"myState" : 1,
"members" : [
{
"_id" : 0,
"name" : "ubuntu:12333",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 263,
"optime" : Timestamp(1487925371, 1),
"optimeDate" : ISODate("2017-02-24T08:36:11Z"),
"electionTime" : Timestamp(1487925371, 2),
"electionDate" : ISODate("2017-02-24T08:36:11Z"),
"self" : true
}
],
"ok" : 1
}
health值為1表示正常運(yùn)行
statuStr表示該數(shù)據(jù)庫(kù)的當(dāng)前角色
將另外兩個(gè)添加進(jìn)來(lái)
添加時(shí)使用的是name值,具體情況具體定
whaike:PRIMARY> rs.add("ubuntu:12334")
{ "ok" : 1 }
whaike:PRIMARY> rs.add("ubuntu:12335")
{ "ok" : 1 }
再次查看狀態(tài)
whaike:PRIMARY> rs.status()
{
"set" : "whaike",
"date" : ISODate("2017-02-24T08:43:32Z"),
"myState" : 1,
"members" : [
{
"_id" : 0,
"name" : "ubuntu:12333",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 664,
"optime" : Timestamp(1487925805, 1),
"optimeDate" : ISODate("2017-02-24T08:43:25Z"),
"electionTime" : Timestamp(1487925371, 2),
"electionDate" : ISODate("2017-02-24T08:36:11Z"),
"self" : true
},
{
"_id" : 1,
"name" : "ubuntu:12334",
"health" : 1,
"state" : 5,
"stateStr" : "STARTUP2",
"uptime" : 16,
"optime" : Timestamp(0, 0),
"optimeDate" : ISODate("1970-01-01T00:00:00Z"),
"lastHeartbeat" : ISODate("2017-02-24T08:43:30Z"),
"lastHeartbeatRecv" : ISODate("2017-02-24T08:43:31Z"),
"pingMs" : 0,
"lastHeartbeatMessage" : "initial sync need a member to be primary or secondary to do our initial sync"
},
{
"_id" : 2,
"name" : "ubuntu:12335",
"health" : 1,
"state" : 5,
"stateStr" : "STARTUP2",
"uptime" : 7,
"optime" : Timestamp(0, 0),
"optimeDate" : ISODate("1970-01-01T00:00:00Z"),
"lastHeartbeat" : ISODate("2017-02-24T08:43:31Z"),
"lastHeartbeatRecv" : ISODate("2017-02-24T08:43:30Z"),
"pingMs" : 0,
"lastHeartbeatMessage" : "initial sync need a member to be primary or secondary to do our initial sync"
}
],
"ok" : 1
}
可以看到添加成功.
我們插入一條數(shù)據(jù)試試
whaike:PRIMARY> db.students.insert({name:"testAAA"})
WriteResult({ "nInserted" : 1 })
whaike:PRIMARY> db.students.find()
{ "_id" : ObjectId("58aff25e7e8f7ae117685350"), "name" : "testAAA" }
插入并查詢(xún)成功!
現(xiàn)在我們將primary關(guān)閉以模擬該數(shù)據(jù)庫(kù)事故
use admin
db.shutdownServer()
exit #退出
重新進(jìn)入一個(gè)數(shù)據(jù)庫(kù)
test1@ubuntu:~$ mongo --port 12334
MongoDB shell version: 2.6.12
connecting to: 127.0.0.1:12334/test
whaike:PRIMARY>
此時(shí)可以看到該復(fù)制集切換成了primary
鍵入rs.status()
查看狀態(tài)
可以看出12333端口所關(guān)聯(lián)的數(shù)據(jù)庫(kù)的health已經(jīng)變?yōu)?粥喜,也就是不可用了,但是之前的STARTUP2變成了primary主導(dǎo).
此時(shí)如果還想擴(kuò)展其他端口關(guān)聯(lián)的副本進(jìn)來(lái)凸主,可以繼續(xù)使用rs.add()
其實(shí)創(chuàng)建Replication的時(shí)候可以一條命令添加多個(gè)復(fù)制集進(jìn)來(lái)
rs.initiate({"_id" : "whaike","members" : [
{"_id" : 0,"host" : "127.0.0.1:12333"},
{"_id" : 1,"host" : "127.0.0.1:12334"},
{"_id" : 2,"host" : "127.0.0.1:12335"},
]})
sharding分片技術(shù)
分片技術(shù),跟關(guān)系數(shù)據(jù)庫(kù)的表分區(qū)類(lèi)似额湘,我們知道當(dāng)數(shù)據(jù)量達(dá)到T級(jí)別的時(shí)候,我們的磁盤(pán)旁舰,內(nèi)存就吃不消了锋华,或者單個(gè)的mongoDB服務(wù)器已經(jīng)不能滿(mǎn)足大量的插入操作,針對(duì)這樣的場(chǎng)景,mongoDB提供的分片技術(shù)來(lái)應(yīng)對(duì)箭窜。
當(dāng)然分片除了解決空間不足的問(wèn)題之外毯焕,還極大的提升的查詢(xún)速度。
盜圖
mongodb將數(shù)據(jù)分?jǐn)偟狡瑓^(qū)上,用戶(hù)代表整個(gè)數(shù)據(jù)庫(kù)纳猫,外面一切訪(fǎng)問(wèn)由通過(guò)用戶(hù)發(fā)起婆咸,而路由則負(fù)責(zé)對(duì)這些操作按一定的規(guī)則分?jǐn)偟讲煌钠瑓^(qū)執(zhí)行,這些規(guī)則保存在配置服務(wù)器中芜辕。
下面來(lái)個(gè)最簡(jiǎn)單的<b>例子</b>
使用mkdir按此路徑建立目錄
/srv/maizi/shardsconf/
/srv/maizi/data1/
/srv/maizi/data2/
啟動(dòng)一個(gè)配置服務(wù)器
sudo mongod --configsvr --port 12339 --dbpath /srv/maizi/shardsconf/ --fork --logpath /var/log/shardconf.log
啟動(dòng)一個(gè)router(路由)尚骄,用于轉(zhuǎn)發(fā)查詢(xún)服務(wù)等
sudo mongos --configdb 127.0.0.1:12339 --fork --port 12338 --logpath /var/log/mongos.log
連接mongos
mongo --port 12338
提示符會(huì)變?yōu)閙ongos>
exit #退出
啟動(dòng)正常
再啟動(dòng)幾個(gè)mongodb instance來(lái)做sharding服務(wù)
sudo mongod --fork --logpath /var/log/shard1.log --dbpath /srv/maizi/data1 --port 12336
sudo mongod --fork --logpath /var/log/shard2.log --dbpath /srv/maizi/data2 --port 12335
此時(shí)進(jìn)程中也許看不到router,但是連接正常侵续,暫且不管他
test1@ubuntu:~$ ps -ax|grep mongod
991 ? Ssl 0:20 /usr/bin/mongod --config /etc/mongod.conf
2692 ? Sl 0:01 mongod --configsvr --port 12339 --dbpath /srv/maizi/shardsconf/ --fork --logpath /var/log/shardconf.log
2737 ? Sl 0:00 mongod --fork --logpath /var/log/shard1.log --dbpath /srv/maizi/data1 --port 12336
2751 ? Sl 0:00 mongod --fork --logpath /var/log/shard2.log --dbpath /srv/maizi/data2 --port 12335
2804 pts/0 S+ 0:00 grep --color=auto mongod
test1@ubuntu:~$ mongo --port 12338
MongoDB shell version: 2.6.12
connecting to: 127.0.0.1:12338/test
mongos> exit
bye
連接router
mongo --port 12338 #鏈接路由
sh.addShard("127.0.0.1:12336") #添加片區(qū)
sh.addShard("127.0.0.1:12335") #添加片區(qū)
use maizi
sh.enableSharding("maizi") #將麥子數(shù)據(jù)庫(kù)enableSharding
sh.shardCollection("maizi.students",{age:1}) #表層面也要enableSharding,還要指定根據(jù)什么規(guī)則來(lái)sharding,這里用年齡
往數(shù)據(jù)庫(kù)中插入10萬(wàn)條數(shù)據(jù)倔丈,可能會(huì)比較耗時(shí)
for(var i=0;i<100000;i++){db.students.insert({age:i%50,name:i});}
查看總條數(shù)
db.students.find().count()
查看如何分布
mongos> sh.status()
--- Sharding Status ---
sharding version: {
"_id" : 1,
"version" : 4,
"minCompatibleVersion" : 4,
"currentVersion" : 5,
"clusterId" : ObjectId("58b387e88aa5cea1a8d87cb9")
}
shards:
{ "_id" : "shard0000", "host" : "127.0.0.1:12336" }
{ "_id" : "shard0001", "host" : "127.0.0.1:12335" }
databases:
{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
{ "_id" : "maizi", "partitioned" : true, "primary" : "shard0001" }
maizi.students
shard key: { "age" : 1 }
chunks:
shard0000 1
shard0001 2
{ "age" : { "$minKey" : 1 } } -->> { "age" : 0 } on : shard0000 Timestamp(2, 0)
{ "age" : 0 } -->> { "age" : 49 } on : shard0001 Timestamp(2, 2)
{ "age" : 49 } -->> { "age" : { "$maxKey" : 1 } } on : shard0001 Timestamp(2, 3)
mongos>
"primary" : "shard0001"
這條數(shù)據(jù)代表就算不sharding,數(shù)據(jù)也會(huì)存儲(chǔ)在這個(gè)sharding0001中
還可看到
maizi.students是這么分的
一共有shard0000和sharding0001兩張表
age從最小值到0分布在shard0000
從0到49分布在shard0001中
從49到最大值也分布在shard0001中
也就是年齡為正數(shù)的都是shard0001中
分別查看一下兩個(gè)sharding中的數(shù)據(jù)量
test1@ubuntu:~$ mongo --port 12335
MongoDB shell version: 2.6.12
connecting to: 127.0.0.1:12335/test
> use maizi
switched to db maizi
> db.students.find().count()
100000
> exit
bye
test1@ubuntu:~$ mongo --port 12336
MongoDB shell version: 2.6.12
connecting to: 127.0.0.1:12336/test
> use maizi
switched to db maizi
> db.students.find().count()
0
>
因?yàn)閿?shù)據(jù)本身并不好且根據(jù)年齡分布的規(guī)則也未設(shè)置好
所以目前所有的數(shù)據(jù)都分布在shard0001,而shard0000中沒(méi)有數(shù)據(jù)
但是sharding應(yīng)該是有效的
為了驗(yàn)證sharding的有效性
切換到mongos來(lái)插入一條年齡為負(fù)數(shù)的數(shù)據(jù)
test1@ubuntu:~$ mongo --port 12338
MongoDB shell version: 2.6.12
connecting to: 127.0.0.1:12338/test
mongos> use maizi
switched to db maizi
mongos> db.students.insert({name:"xiaotingzi",age:-26})
WriteResult({ "nInserted" : 1 })
然后切換到shard0000也就是12336端口進(jìn)行查看<small>(關(guān)于這點(diǎn)可以在狀態(tài)分布的結(jié)果中状蜗,從shards字段看出)</small>
test1@ubuntu:~$ mongo --port 12336
MongoDB shell version: 2.6.12
connecting to: 127.0.0.1:12336/test
> use maizi
switched to db maizi
> db.students.find()
{ "_id" : ObjectId("58b395c5a04059cd2fbb6ae0"), "name" : "xiaotingzi", "age" : -26 }
>
所以需五,sharding有效,通過(guò)規(guī)則區(qū)分?jǐn)?shù)據(jù)已經(jīng)成功
關(guān)于shard也可以查看文檔知識(shí)庫(kù)MongoDB 3.0 常見(jiàn)集群的搭建(主從復(fù)制,副本集轧坎,分片....)
數(shù)據(jù)備份與恢復(fù)
1宏邮、文件系統(tǒng)快照
2、mongodb提供的工具
1.mongodump #更靈活的操作缸血,缺點(diǎn):慢
2.mongorestore
詳情見(jiàn)官網(wǎng)MongoDB Backup Methods
安全security checklist
1蜜氨、Authentication
2、Authorization
mongodb默認(rèn)的內(nèi)部通信是明文的属百,所以需要安置在可信任的環(huán)境中.
如果要使用用戶(hù)名和密碼進(jìn)行訪(fǎng)問(wèn)則需要自己在配置文件中配置
詳見(jiàn)官網(wǎng)Security Checklist
服務(wù)器端腳本
mongodb2.4版本之后服務(wù)端使用的是js的V8引擎來(lái)解析服務(wù)器端的JS腳本
用回之前的數(shù)據(jù)庫(kù)mongo test
通過(guò)where可以使用js函數(shù)
db.students.find({$where:function(){return obj.age>22;}})
db.students.find({$where:function(){return typeof obj.courses != "undefined" && obj.courses.length >=1;}})
db.students.find({$where:"obj.age>22"}) #也可以直接使用字符串
注意:$where后面的內(nèi)容返回的一定是一個(gè)布爾型
詳情見(jiàn)官網(wǎng)Server-side JavaScript
可以自定義JavaScript函數(shù)保存在數(shù)據(jù)庫(kù)中,使用時(shí)用db.eval(...)調(diào)用
db.system.js.save(
{_id:"echoFunction",
value:function(x){return x;}
}
)
db.eval("echoFunction('test')")
詳見(jiàn)官網(wǎng)Store a JavaScript Function on the Server
事務(wù)
<b>ACID</b>
A:原子型 :
C:一致性 :更新一般服務(wù)器問(wèn)題记劝,不會(huì)出現(xiàn)臟數(shù)據(jù)
C:隔離性 :更新過(guò)程其他進(jìn)程無(wú)法讀取
D:分卷技術(shù) :將要進(jìn)行的操作寫(xiě)入日志里,日志記錄完畢之后會(huì)提示操作成功族扰,此時(shí)其實(shí)并沒(méi)有成功厌丑,如果這時(shí)數(shù)據(jù)庫(kù)異常,則日志里還有記錄渔呵,可以保證數(shù)據(jù)完整怒竿。之后再有該日志來(lái)修改數(shù)據(jù)庫(kù)數(shù)據(jù).
所往一個(gè)document更新數(shù)據(jù) ,符合一致性扩氢,要么更新的幾個(gè)參數(shù)一起OK耕驰,要么都更新不上,所以單個(gè)文檔支持事務(wù)录豺。
但是多個(gè)文檔則不支持
但是可以通過(guò)某種算法或者技術(shù)來(lái)實(shí)現(xiàn)它
詳見(jiàn)官網(wǎng)Perform Two Phase Commits
雖然可以實(shí)現(xiàn)朦肘,但是依舊要避免出現(xiàn)這種情況。
數(shù)據(jù)聚合
在這里使用一個(gè)可視化軟件robomongo-1.0.0-rc1-linux-x86_64-496f5c2.tar.gz
解壓之后進(jìn)入bin目錄直接執(zhí)行robomongo
即可打開(kāi),配置只有一個(gè)連接本地或者遠(yuǎn)程双饥,端口默認(rèn)媒抠。
這個(gè)工具還不錯(cuò)
鍵入命令
db.students.count({age:{$gt:22}}) #統(tǒng)計(jì)年齡大于22的一共有多少個(gè),最簡(jiǎn)單的聚合
鍵入
db.students.distinct("age") #統(tǒng)計(jì)數(shù)據(jù)庫(kù)中所有的年齡咏花,會(huì)自動(dòng)去重
顯示方式可以分好幾種趴生,我使用的是紅色標(biāo)注的字符模式
統(tǒng)計(jì)每個(gè)年齡層下有多少人
db.students.group({
key:{age:1},
reduce:function(cur,result){result.count+=1},
initial:{count:0}
})
db.students.group({
key:{age:1}, //分組依據(jù)
cond:{age:{$exists:true},gpa:{$exists:true}}, //這里表示只操作有數(shù)據(jù)的部分
reduce:function(cur,result){result.count+=1;result.total_gpa += cur.gpa;result.ava_gpa = result.total_gpa / result.count;}, //顯示字段與運(yùn)算規(guī)則
initial:{count:0,total_gpa:0} //初始值
})
數(shù)據(jù)聚合流水線(xiàn)
db.students.aggregate(
[
{$match:{age:{$exists:true}}},
{$group:{_id:"$age",count:{$sum:1},total_gpa:{$sum:"$gpa"}}}
]
)
$age
表示從上一個(gè)的輸出結(jié)果中拿到age,其他字段類(lèi)似
流水線(xiàn)的分組聚合方式中,各個(gè)工位在數(shù)組中以逗號(hào)分隔苍匆,工位是可以增加的
db.students.aggregate(
[
{$match:{age:{$exists:true}}},
{$group:{_id:"$age",count:{$sum:1},total_gpa:{$sum:"$gpa"}}},
{$project:{_id:1,count:1,ava_gpa:{$divide:["$total_gpa","$count"]}}}
]
)
db.students.aggregate(
[
{$match:{age:{$exists:true}}}, //拿到符合要求的數(shù)據(jù)
{$group:{_id:"$age",count:{$sum:1},total_gpa:{$sum:"$gpa"}}}, //根據(jù)年齡分組刘急,統(tǒng)計(jì)
{$project:{_id:1,count:1,ava_gpa:{$divide:["$total_gpa","$count"]}}}, //投影與計(jì)算平均值
{$sort:{ava_gpa:-1}} #排序
]
)
上一步的計(jì)算平均值可以使用內(nèi)置的函數(shù)avg,效果一樣
db.students.aggregate(
[
{$match:{age:{$exists:true}}},
{$group:{_id:"$age",count:{$sum:1},ava_gpa:{$avg:"$gpa"}}},
{$sort:{ava_gpa:-1}}
]
)
不同年齡的選課統(tǒng)計(jì)
db.students.aggregate(
[
{$match:{age:{$exists:true},courses:{$exists:true}}},
{$project:{age:1,courses_count:{$size:"$courses"}}}, //在這里先對(duì)數(shù)據(jù)進(jìn)行投影可以減少數(shù)據(jù)量浸踩,減小內(nèi)存使用叔汁,避免浪費(fèi)資源
{$group:{_id:"$age",acc:{$avg:"$courses_count"}}},
{$sort:{acc:-1}}
]
)
不能年齡層選課種類(lèi)統(tǒng)計(jì)
db.students.aggregate(
[
{$match:{age:{$exists:true},courses:{$exists:true}}},
{$project:{age:1,"courses.name":1}}, //直接投影課程的名字
{$unwind:"$courses"}, //展開(kāi)數(shù)組
{$group:{_id:"$age",c_names:{$addToSet:"$courses.name"}}}, //將同齡的人選的所有課程進(jìn)行合并
{$project:{_id:1,c_names:1,cc:{$size:"$c_names"}}}, //統(tǒng)計(jì)
{$sort:{cc:-1}} //最后根據(jù)cc排序
]
)
詳細(xì)信息可以查看官方文檔Aggregation Pipeline Operators
MapReduce
流水線(xiàn)方式的操作受文檔大小限制,且在內(nèi)存中執(zhí)行民轴,不適合大數(shù)據(jù)處理攻柠,但是mapReduce是將數(shù)據(jù)存儲(chǔ)在一個(gè)collection中進(jìn)行處理,可以操作很大量的數(shù)據(jù)后裸,適合大數(shù)據(jù)處理
db.students.mapReduce(
function(){ //map函數(shù)瑰钮,起到過(guò)濾器的作用,emit發(fā)射數(shù)據(jù)
emit(this.age,this.gpa);
},
function(key,values){ //reduce函數(shù) 這里values如果有多個(gè)值則以數(shù)組形式保存
return Array.sum(values)/values.length;
},
{ //options
query:{age:{$exists:true},gpa:{$exists:true}}, //過(guò)濾條件
out:{inline:1} //輸出
}
)
db.students.mapReduce(
function(){ //map函數(shù),起到過(guò)濾器的作用,emit發(fā)射數(shù)據(jù)
emit(this.age,this.gpa); //發(fā)射出來(lái)的兩個(gè)參數(shù)中微驶,age作為key浪谴,gpa作為value,如果key下的value不止一個(gè)值,那么才會(huì)進(jìn)入reduce進(jìn)行進(jìn)一步處理,且傳入的value是一個(gè)數(shù)組因苹,如果只有一個(gè)則不會(huì).發(fā)射出的數(shù)據(jù)受到單個(gè)bson的object大小限制(16M)且最多只能發(fā)射一半(8M)
},
function(key,values){ //reduce函數(shù) 這里values如果有多個(gè)值則以數(shù)組形式保存
return Array.sum(values)/values.length;
},
{ //options
query:{age:{$exists:true},gpa:{$exists:true}}, //過(guò)濾條件
out:{inline:1} //輸出,out:"avg_gpa" //輸出到一個(gè)collections中,可以持久化保存苟耻,可以用于真正的大數(shù)據(jù)處理,inline操作在內(nèi)存中運(yùn)行受內(nèi)存大小限制,也受到單個(gè)bson的object大小限制(16M)且最多只能發(fā)射一半(8M)
}
)
db.students.mapReduce(
function(){
if(this.age>20){
emit("old",this.gpa);
}else{
emit("yong",this.gpa)
}
},
function(key,values){
return {gpa_array:values};
},
{
query:{age:{$exists:true},gpa:{$exists:true}},
out:{inline:1}
}
)
db.students.mapReduce(
function(){
var course_names = this.courses.map(
function(course){
return course.name
});
var intersection_names = course_names.filter(
function(c_name){
return params.indexOf(c_name) != -1
});
var diff_names = course_names.filter(
function(c_name){
return params.indexOf(c_name) == -1
});
diff_names.forEach(
function(c_name){
emit(c_name,intersection_names.length / course_names.length)
});
},
function(key,values){
var total = values.reduce(function(p,c){
return p + c;
});
return total;
},
{
query:{courses:{$exists:true}},
out:{inline:1},
scope:{params:["MongoDB"]}
}
)
官方文檔mapReduce