1. 程式人生 > 實用技巧 >MongoDB基礎1

MongoDB基礎1

MongoDB基礎1

API:

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簡介

是什麼?

NoSQL簡介

為什麼需要NoSQL

NoSQL優缺點

基本概念

規則

資料型別

增刪改

條件查詢

正則查詢、陣列查詢、內嵌查詢

統計、排序

遊標

聚合框架

簡介

聚合框架的HelloWorld

管道操作符

$match

$project

$group

$unwind

$sort

常見聚合函式

MapReduce

MapReduceHelloWorld

MapReduce的更多可選鍵

聚合命令

group

理解文件儲存機制

索引

固定集合

GridFS

簡介

優缺點

儲存檔案

MongoDB簡介

是什麼?

MongoDB是一個使用C++編寫的、開源的、面向文件的NOSQL資料庫,也是當前最熱門的NoSQL資料庫之一。

NoSQL簡介

NoSQL的意思是“不僅僅是SQL”,是目前流行的“非關係型資料庫”的統稱。常見的NoSQL資料庫如:Redis、CouchDB、MongoDB、Hbase、Cassandra等。

為什麼需要NoSQL

簡單的說,就是為了解決web2.0時代,出現的三高要求:

1、對資料庫高併發讀寫需求

2、對海量資料的高效率儲存和訪問需求

3、對資料庫的高擴充套件和高可利用的需求

NoSQL優缺點

基本概念

規則

資料型別

增刪改

# 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支援
# 最大響應訊息的大小

聚合框架的HelloWorld

# 插入預備資料:
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等,注意:不同的順序可能會直接影響最終的結果

$match

用於對文件集合進行篩選,裡面可以使用所有常規的查詢操作符。通常會放置在管道最前面的位置,理由如下:

1、快速將不需要的文件過濾,減少後續操作的資料量

2、在投影和分組之前做篩選,查詢可以使用索引。

$project

project放在$match之前,則match裡的過濾條件必須包含在project裡,否則查不到任何內容

用來從文件中提取欄位,可以指定包含和排除欄位,也可以重新命名欄位,不寫預設取所有。比如要將studentId改為sid,如下:

db.sources.aggreate([{"$project":{"sid":"$studentId"}}]})

管道操作符還可以使用表示式,以滿足更復雜的需求。

$project的數學表示式

支援的操作符和語法:

$add:[expr1[,expr2,..exprn]]
$subtract:[expr1,expr2]
$multiply:[expr1[,expr2,...exprn]]
$divice:[expr1,expr2]
$mod:[expr1,expr2]

# 例如給成績集體加20分
{"$project":{"newScore":{"$add":["$score":20]}}}

$project的日期表示式

聚合框架包含了一些用於提取日期資訊的表示式,如下:

# 注意:這些只能操作日期型的欄位,不能操作資料,用法案例
{"$project":{"opeDay":{"$dayOfMonth":"$recoredTime"}}} # 從redoredTime欄位取出月重新命名為opeDay

$project的字串表示式

$substr:[expr,開始位置,要取的位元組個數]
$concat:[expr1[,expr2,...exprn]]
$toLower:expr
$toUpper:expr
# 例如:{"$project":{"sid":{"$concat":["$studentId","cc"]}}}}

$project的邏輯表示式

$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

用來將文件依據特定欄位的不同值進行分組。選定了分組欄位過後,就可以把這些欄位傳遞給$group函式的"_id"欄位了。例如:

db.score.aggregate({"$group":{"_id":"$studentId"}}) 或者
db.score.aggregate({"$group":{"_id":{"sid":"studentId","score":"$score"}}})

$group支援的操作符:

$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"}}}])

$unwind

用來把陣列中的每個值拆分成為單獨的文件。

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"
}

$sort

可以根據任何欄位進行排序,與普通查詢中的語法相同。如果要對大量的文件進行排序,強烈建議在管道的第一階段進行排序,這時可以使用索引。

常見聚合函式

count

用於返回集合中文件的數量,不能跟著aggregate後

db.col.find().count()

distinct

找出給定鍵的所有不同值,使用時必須指定集合和鍵,例如:

db.runCommand({"distinct":"users","key":"userId"})

MapReduce

# 語法
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來作為查詢語言,因此能表達任意的邏輯,但是它執行的非常慢,不應該用在實時的資料分析中。

MapReduce的HelloWorld

# 找出集合中所有的鍵值,並統計每個鍵出現的次數。

# 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
}

MapReduce的更多可選鍵

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
    }
}

聚合命令

group

這個和管道里的$group

用來對集合進行分組,分組過後,再對每一個分組內的文件進行聚合

理解文件儲存機制

將文件插入到MongoDB的時候,文件是按照插入的順序,依次在磁碟上相鄰儲存。因此,一個文件變大了,原來的位置要是放不下這個文件了,就需要把這個文件移動到集合的另外一個位置,通常是最後,能放下這個文件的地方。

MongoDB移動文件的時候,會自動修改集合的填充因子(padding factor),填充因子是為新文件預留的增長空間,不能手動設定填充因子。

1、填充因子開始可能是1,也就是為每個文件分配精確的空間,不預留增長空間。

2、當有文件超過而被迫移動文件的時候,填充因子可能會增大。

3、當集合不在有文件移動的時候,填充因子會慢慢變小。

MongoDB進行文件移動是非常慢的

移動文件的時候,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

簡介

GridFS時是MongoDB用來儲存大型二進位制檔案的一種儲存機制。特別適合用在儲存一些不常改變,但是經常需要連續訪問的大檔案的情況。GridFS沒有專門的大型分散式檔案系統那麼強大,如果你不是那麼需要大型分散式檔案系統使用GridFS還是可以的。

GridFS用於儲存和恢復那些超過16M(BSON檔案限制)的檔案(如:圖片、音訊、視訊等)。它會將大檔案物件分割成多個小的chunk(檔案片段),一般為256k/個,每個chunk將作為MongoDB的一個文件被儲存在chunks集合中。

優缺點

儲存檔案