MongoDB基礎1
https://docs.mongodb.com/v4.0/reference/operator/query/expr/
https://www.bilibili.com/video/BV11b41127Lg?p=1
https://www.bilibili.com/video/BV1Rt41117mT?p=1
MongoDB是一個使用C++編寫的、開源的、面向文件的NOSQL資料庫,也是當前最熱門的NoSQL資料庫之一。
NoSQL的意思是“不僅僅是SQL”,是目前流行的“非關係型資料庫”的統稱。常見的NoSQL資料庫如:Redis、CouchDB、MongoDB、Hbase、Cassandra等。
# mongoDB只允許一個文件最大為16M,可以使用以下命令檢視文件大小 Object.bsonsize({"name":"毛毛"}) # 輸出 20 # 即20個位元組 var x = {"name":"keke","sex":1} Object.bsonsize(x) # 檢視狀態 db.stats() db.xinhua.stats()
# 檢視所有文件 db.col.find() # 檢視集合中第一個文件 db.col.findOne({條件物件}) # 文件替換,這是替換整個文件,不常用 db.col.update(條件,新的文件) db.xuser.update({"userId":"s0","course":"課程0"},{"userId":"s0","course":"課程0","score":99}) # 更新修改器,用來做複雜的更新操作 db.col.update(條件,{"$set":{}}) db.xuser.update({"age":22},{"$set":{"work":"cxy"}},0,1) # 引數0表示upset:找到符合條件的文件就更新,否則會以這個條件來更新文件來建立新文件 # 指定update方法的第三個引數為true,可表示是upset # 引數1表示改條件的文件全量更新 db.xuser.update({"name":"u2"},{"$inc":{"age":5}}) db.xuser.update({"name":"毛毛"},{"$push":{"like":"play"}}) db.xuser.update({"name":"毛毛"},{"$push":{"like":{"$each":["sleep","sport"]}}}) # slice是和$push $each一起使用的 db.xuser.update({"name":"毛毛"},{"$push":{"like":{"$each":["LOL","study"],"$slice":-3}}}) # $是按照索引來取,把毛毛的like資料裡的第2個元素改為"AVM" db.xuser.update({"like.2":"sport","name":"毛毛"},{"$set":{"like.$":"AVM"}})
# save如果存在就修改,不存在就新增。insert只能插入不重複_id的文件。
db.col.find function (query, fields, limit, skip, batchSize, options) { ... return cursor; } # 查詢年齡大於20歲 db.xuser.find({"age":{"$gt":20}}) # 投影,過濾id欄位,只取3條 db.xuser.find({},{"_id":0},3) # 大於20小於27歲,預設是and條件(隱式) db.xuser.find({"age":{"$gt":20,"$lt":27}}) db.xuser.find({"name":{"$ne":"u1"}}) # 名稱不為“u1” # 顯式指明,陣列內關係為and或or db.xuser.find({"$and":[{name:"u3"},{"age":{"$gt":20}}]}) db.xuser.find({"$or":[{name:"u3"},{"age":{"$gt":20}}]}) # $not不能最為頂級操作符,且其後只能跟document或正則 錯誤寫法:db.xuser.find({"$not":{"name":"u1"}}) 錯誤寫法:db.xuser.find({"name":{"$not":"u1"}}) 跟正則:db.xuser.find({"name":{"$not":/u1/}}) # 名稱不為u1 跟document:db.xuser.find({"age":{"$not":{"$gt":25}}}) # 年齡不大於25歲 # $mod拿age和100求餘,餘數為2的文件 db.xuser.find({"age":{"$mod":[20,2]}}) # $in 年齡不在22、23的文件 db.xuser.find({"age":{"$not":{"$in":[22,23]}}}) db.xuser.find({"age":{"$nin":[22,23]}}) # $all 陣列內必須包含LOL和study的文件 db.xuser.find({"like":{"$all":["LOL","study"]}}) # $exists 查詢必須存在like鍵的文件 db.xuser.find({"like":{"$exists":1}}) # null 不僅能查不存在鍵work的文件,還能查鍵work為null的文件 db.xuser.find({"work":null}) db.xuser.find({"work":{"$in":[null],"$exists":1}})
# 正則查詢 db.xuser.find({"name":/^u/}) # 單個元素匹配 db.xuser.find({"name":"u1"}) # 多個元素匹配 db.xuser.find({"like":{$all:["LOL","study"]}}) # 可以使用索引指定查詢陣列特定位置,{"key.索引號":"value"} db.xuser.find({"like.0":"AV"})
# 如果內嵌是個陣列,則x.y中y就是索引,如果內嵌是個文件,則x.y中y就是key
# 使用count()函式是統計find({})的結果,不受其他過濾條件影響 >db.xuser.find().limit(3).count() 4 >db.xuser.find().limit(3).count(true) 3
MongoDB的聚合框架,主要用來對集合中的文件進行變換和組合,從而對資料進行分析以加以利用。
聚合框架的基本思路是:採用多個構件來建立一個管道,用於對一連串的文件進行處理。這些構建包括:篩選(filtering)、投影(projecting)、分組(grouping)、排序(sorting)、限制(limiting)和跳過(skipping)。
db.集合.aggregate(構件1,構件2...)
# 注意由於聚合的結果要返回客戶端,因此聚合結果必須限制在16M以內,這是MongoDB支援
# 最大響應訊息的大小
# 插入預備資料: for(var i=0;i<100;i++) { for(var j=0;j<4;j++) { db.xuser.insert({"userId":"s"+i,"course":"課程"+j,"score":Math.random()*100}) } }
# 1、找到所有考了80分以上的學生,不區分課程 # {"$match":{"score":{"$gte":80}}} db.getCollection('xuser').aggregate([{"$match":{"score":{"$gte":80}}}]) # 2、將每個學生的名字投影出來,注意順序,如果$project放在前面查詢 # 結果為空,因為這是一個管道,按照順序過濾 # {"$project":{"userId":1}} db.getCollection('xuser').aggregate([ {"$match":{"score":{"$gte":80}}}, {"$project":{"userId":1}} ]) # 3、對學生名字進行排序,某個學生的名字出現一次,就給他加1,注意_id是必須的,其value就是你要分組的欄位 # {"$group":{"_id":"$userId","count":{"$sum":1}}} db.getCollection('xuser').aggregate([ {"$match":{"score":{"$gte":80}}}, {"$project":{"userId":1}}, {"$group":{"_id":"$userId","count":{"$sum":1}}} ]) # 4、對結果集分數的降序進行排列 # {"$sort":{"count":-1}} db.getCollection('xuser').aggregate([ {"$match":{"score":{"$gte":80}}}, {"$project":{"userId":1}}, {"$group":{"_id":"$userId","count":{"$sum":1}}}, {"$sort":{"count":-1}} ]) # 5、限制limit # ${limit:5} db.getCollection('xuser').aggregate([ {"$match":{"score":{"$gte":80}}}, {"$project":{"userId":1}}, {"$group":{"_id":"$userId","count":{"$sum":1}}}, {"$sort":{"count":-1}}, {"$limit":5} ])
每個操作符接受一系列的文件,對這些文件做相應的處理,然後把轉換後的文件作為結果傳遞給下一個操作符。最後一個操作符會將結果返回。
不同的管道操作符,可以按照任意順序,任意個數組合在一起使用。如上$project、$sort、$group等,注意:不同的順序可能會直接影響最終的結果。
用於對文件集合進行篩選,裡面可以使用所有常規的查詢操作符。通常會放置在管道最前面的位置,理由如下:
若project放在$match之前,則match裡的過濾條件必須包含在project裡,否則查不到任何內容。
用來從文件中提取欄位,可以指定包含和排除欄位,也可以重新命名欄位,不寫預設取所有。比如要將studentId改為sid,如下:
db.sources.aggreate([{"$project":{"sid":"$studentId"}}]})
$add:[expr1[,expr2,..exprn]]
$subtract:[expr1,expr2]
$multiply:[expr1[,expr2,...exprn]]
$divice:[expr1,expr2]
$mod:[expr1,expr2]
# 例如給成績集體加20分
{"$project":{"newScore":{"$add":["$score":20]}}}
# 注意:這些只能操作日期型的欄位,不能操作資料,用法案例
{"$project":{"opeDay":{"$dayOfMonth":"$recoredTime"}}} # 從redoredTime欄位取出月重新命名為opeDay
$substr:[expr,開始位置,要取的位元組個數]
$concat:[expr1[,expr2,...exprn]]
$toLower:expr
$toUpper:expr
# 例如:{"$project":{"sid":{"$concat":["$studentId","cc"]}}}}
$cmp:[expr1,expr2] #比較兩個表示式,0表示相等,正數前面的大,負數後面的大
$strcasecmp:[string1,string2] # 比較兩個字串,區分大小寫,只對由羅馬字元[I,II,III,IV,VI]組成的字串有效
$eq $ne $gt $gte $lt $lte:[expr1,expr2]
$cond:[booleanExpr,trueExpr,falseExpr]:#三目運算子。如果boolean表示式為true,返回true表示式,否則返回false表示式。
$ifNull:[expr,otherExpr]:#如果expr為null,返回otherExpr,否則返回expr
# 例如
db.scores.aggregate([{"$project":{"newScore":["$cmp":["$studentId","sss"]]}}])
用來將文件依據特定欄位的不同值進行分組。選定了分組欄位過後,就可以把這些欄位傳遞給$group函式的"_id"欄位了。例如:
db.score.aggregate({"$group":{"_id":"$studentId"}}) 或者
db.score.aggregate({"$group":{"_id":{"sid":"studentId","score":"$score"}}})
$sum:value #對於每個文件,將value與計算結果相加
$avg:value #返回每個分組的平均值
$max:expr #返回分組內的最大值
$min:expr #返回分組內的最小值
$first:expr #返回分組的第一個值,忽略其他值,一般只有排序後,明確知道資料順序的時候,此操作才有意義
$last:expr #與上面一個相反,返回分組的最後一個值
$addToSet:expr # 如果當前陣列中不包括expr,那就將它加入到陣列中
$push:expr # 把expr加入到陣列中
# 案例
db.getCollection('xuser').aggregate([{"$group":{"_id":"$userId","avg":{"$avg":"$score"}}}])
db.getCollection('xuser').insert({"userId":"小米","like":["apple","play","eat"]}) db.getCollection('xuser').aggregate([{"$unwind":"$like"}]) # 返回結果 /* 1 */ { "_id" : ObjectId("5ef98dc0388090bf5b20fc4a"), "userId" : "小米", "like" : "apple" } /* 2 */ { "_id" : ObjectId("5ef98dc0388090bf5b20fc4a"), "userId" : "小米", "like" : "play" } /* 3 */ { "_id" : ObjectId("5ef98dc0388090bf5b20fc4a"), "userId" : "小米", "like" : "eat" }
可以根據任何欄位進行排序,與普通查詢中的語法相同。如果要對大量的文件進行排序,強烈建議在管道的第一階段進行排序,這時可以使用索引。
db.col.find().count()
db.runCommand({"distinct":"users","key":"userId"})
# 語法 db.collection.mapReduce( function() {emit(key,value);}, //map 函式 function(key,values) {return reduceFunction}, //reduce 函式 { out: collection, query: document, sort: document, limit: number } ) # 執行結果引數 result:儲存結果的collection的名字,這是個臨時集合,MapReduce的連線關閉後自動就被刪除了。 timeMillis:執行花費的時間,毫秒為單位 input:滿足條件被髮送到map函式的文件個數 emit:在map函式中emit被呼叫的次數,也就是所有集合中的資料總量 ouput:結果集合中的文件個數(count對除錯非常有幫助),out: { inline: 1 }設定了 {inline:1} 將不會建立集合,整個 Map/Reduce 的操作將會在記憶體中進行。注意,這個選項只有在結果集單個文件大小在16MB限制範圍內時才有效。 ok:是否成功,成功為1 err:如果失敗,這裡可以有失敗原因,不過從經驗上來看,原因比較模糊,作用不大
在MongoDB的聚合框架中,還可以使用MapReduce,它非常強大靈活,但具有一定的複雜性,專門用於實現一些複雜的聚合功能。
MongoDB中的MapReduce使用JavaScript來作為查詢語言,因此能表達任意的邏輯,但是它執行的非常慢,不應該用在實時的資料分析中。
# 找出集合中所有的鍵值,並統計每個鍵出現的次數。 # 1、Map函式必須使用emit函式來返回要處理的值,示例如下: var map = function() { # map函式會對每一個文件進行處理,這裡的this即指當前文件 for(var key in this) { # emit裡的這個key和下面reduce引數裡的key是對應的, # emits儲存的是所有的emit(key,value)裡的當前key對應的value陣列 emit(key,{count:1}); } } # 2、reduce函式需要處理Map階段或者是前一個reduce的資料,因此reduce返回的 # 文件必須要能作為recude的第二個引數的一個元素,示例如下: var reduce = function(key,emits) { var total = 0; for(var i in emits) { total += emits[i].count; } return {"count":total} } # 連貫起來,在shell執行如下: db.xuser.mapReduce( function() { for(var key in this) { emit(key,1); } }, function(key, values) {return Array.sum(values)}, { out:"post_total" } ) # 執行結果如下: { "result" : "post_total", "timeMillis" : 20.0, "counts" : { "input" : 401, "emit" : 401, "reduce" : 100, "output" : 101 }... # input:有401個符合條件的查詢,emit:在map函式中生成了5個鍵值對,最後使用 # reduce函式將相同的鍵值分為101組 db.post_total.find({}).limit(2) # 執行結果: /* 1 */ { "_id" : "s0", "value" : 4.0 } /* 2 */ { "_id" : "s1", "value" : 4.0 }
finalize:function(key,value){return value} # 可以將reduce的結果傳送到finalize,這是整個處理的最後一步 keeptemp:boolean # 是否在連線關閉的時候,儲存臨時結果集合 query:document # 在傳送給map前對文件進行過濾 sort:document #在傳送給map前對文件進行排序 limit:integer #在傳送map函式的文件數量上限 scope:document #可以在javascript中使用的變數 verbose:boolean #是否記錄詳細的伺服器日誌 # 案例 db.xuser.mapReduce( function() { emit(this.userId,1);}, function(key, values) {return Array.sum(values)}, { out:"post_total" , finalize:function(key,value){ return {'mk':key,'mv':value} } } ) db.post_total.find({}).limit(1) # 返回結果: /* 1 */ { "_id" : "s0", "value" : { "mk" : "s0", "mv" : 4.0 } }
用來對集合進行分組,分組過後,再對每一個分組內的文件進行聚合
將文件插入到MongoDB的時候,文件是按照插入的順序,依次在磁碟上相鄰儲存。因此,一個文件變大了,原來的位置要是放不下這個文件了,就需要把這個文件移動到集合的另外一個位置,通常是最後,能放下這個文件的地方。
MongoDB移動文件的時候,會自動修改集合的填充因子(padding factor),填充因子是為新文件預留的增長空間,不能手動設定填充因子。
1、填充因子開始可能是1,也就是為每個文件分配精確的空間,不預留增長空間。
移動文件的時候,MongoDB需要將文件原先佔用的空間釋放掉,然後將文件寫入新的空間,相對費時,尤其是文件比較大,又頻繁需要移動的話,會嚴重影響效能。
# 檢視索引 >db.getCollection('xuser').getIndexes() /* 1 */ [ { "v" : 1, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "xinhua.xuser" } ] >db.getCollection('xuser').find({}).explain() # https://docs.mongodb.com/v4.0/reference/explain-results/
# 建立索引,1表示升序,-1表示降序 >db.xinhua.createIndex({"name":1}) # 預設索引名稱,mongo會自動生成一個名稱 /* 1 */ { "createdCollectionAutomatically" : false, "numIndexesBefore" : 1, "numIndexesAfter" : 2, "ok" : 1.0 } >db.getCollection('xuser').getIndexes() [ { "v" : 1, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "xinhua.xuser" }, { "v" : 1, "key" : { "name" : 1.0 }, "name" : "name_1", "ns" : "xinhua.xuser" } ] >db.xinhua.createIndex({"age":1},{"name":"myAgeIndex"}) >db.getCollection('xuser').getIndexes() [ { "v" : 1, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "xinhua.xinhua" }, { "v" : 1, "key" : { "GUNS_XINHUA_VERSION" : 1.0 }, "name" : "GUNS_XINHUA_VERSION_1", "ns" : "xinhua.xinhua" }, { "v" : 1, "key" : { "age" : 1.0 }, "name" : "myAgeIndex", "ns" : "xinhua.xinhua" } ] # 刪除索引 db.xinhua.dropIndex({"name":1}) # 按照建立的欄位刪除索引 db.xuser.dropIndex("myAgeIndex") # 如果自己建立了索引名稱,可以使用名稱刪
MongoDB 固定集合(Capped Collections)是效能出色且有著固定大小的集合,對於大小固定,我們可以想象其就像一個環形佇列,當集合空間用完後,再插入的元素就會覆蓋最初始的頭部的元素!一般用於限制:最新博文(只顯示前10篇),最新歌曲,等設定大小,高效利用。
# 建立時需指定capped和size >db.createCollection("cappedLogCollection",{capped:true,size:10000}) # 還可以指定文件個數,加上max:1000屬性: >db.createCollection("cappedLogCollection",{capped:true,size:10000,max:1000})
GridFS時是MongoDB用來儲存大型二進位制檔案的一種儲存機制。特別適合用在儲存一些不常改變,但是經常需要連續訪問的大檔案的情況。GridFS沒有專門的大型分散式檔案系統那麼強大,如果你不是那麼需要大型分散式檔案系統使用GridFS還是可以的。
GridFS用於儲存和恢復那些超過16M(BSON檔案限制)的檔案(如:圖片、音訊、視訊等)。它會將大檔案物件分割成多個小的chunk(檔案片段),一般為256k/個,每個chunk將作為MongoDB的一個文件被儲存在chunks集合中。