0%

mongoDB 基礎


啟動mongoDB server

1
2
3
4
5
mongod --dbpath "路徑" //指定資料的存放路徑
mongod --logpath "路徑" //指定log檔的存放路徑(必須是.log),與--dbpath可以一起執行
mongod --port "port" //指定mongodb伺服器的端口
mongod --fork --logpath //讓mongo server在背景執行(不用再另外開個cmd,所以需要指定log路徑來輸出log),只能在Unix和Mac環境使用
net start MongoDB //跟上面一樣讓mongo server在背景執行(windows環境)
  • 終止mongo server
1
net stop MongoDB //windows
  • 使用mongo shell終止mongo server
1
2
use admin //進入admin collection
db.shutdownServer()

MongoDB設定檔

  • 可以指定mongoDB讀取自己產生的設定檔,設定檔可以事先設定簡單的設定,指令如下
1
mongod --config "路徑/檔名.cfg"
  • 設定檔內容範例
1
2
3
4
5
storage:
dbpath: "路徑"
systemLog:
destination: file
path: "路徑"
1
2
3
4
5
mongo //進入mongo shell
show dbs //顯示所有的資料庫
use "資料庫名稱" //進入該資料庫,如此資料庫尚未存在會自動生成,但在插入資料前不會實際被建立
db.status() //列出資料庫的狀態,包含collections數量、大小等資訊
db."collection名稱".drop() //刪除該collection

匯入Json檔到資料庫(import)

1
2
3
mongoimport 檔案名稱 -d 資料庫名稱 -c collection名稱 --jsonArray --drop
// --jsonArray代表json檔案內是包含多個documents的資料,這樣才會插入整個陣列
// --drop代表如果collection已經存在,會先刪除該collection在建立一個新的插入資料

MongoDB shell 基礎指令

Read

1
2
3
db."collection名稱".find(filter,option) //列出該collection所有的document
db."collection名稱".find(filter,option).pretty()//同上,但會自動排版
db."collection名稱".findOne(filter,option) // 只列出一筆
  • find實際上不會列出所有的documents(預設20筆),而是會給一個cursor object,可以在shell輸入it指令來繼續尋訪剩下的documents,或是在find()後面加上.toArray()
  • $gt代表大於,例如{distance: {$gt: 1000}},distance大於1000的都會被列出來
  • filter範例 : {keyname: value}
  • find會列出documents內的所有key value,但有時在應用時不需要全部的key,這時可以如 db.passenger.find({},{name: 1}),只會列出name和_id(預設一定會列出)這兩個key,如不想列出_id只要將{name: 1}改成{name:1, _id:0}

Comparison Operators

  • $gt : 大於
  • $gte : 大於等於
  • $eq : 等於
  • $ne : 不等於
  • $lt : 小於
  • $lte : 小於等於
  • $in : 出現在陣列裡的數值會被列出來
  • $nin : 與$in相反,出現在陣列裡的數值不會被列出來

範例如下

1
2
3
db.movies.find({runtime: {$gt: 30}}) //runtime大於30的documents會被找出來
db.movies.find({"rating.average" : {$lt: 9}}) //搜尋巢狀結構時要用雙引號包起來,rating裡面的average小於9的會被找出來
db.movies.find({runtime: {$in: [30,42]}}) //runtime是30或42的會被找出來

Logical Operators

  • $and : 結合複數條件,條件都符合才會被列出來
  • $not : 與條件符合以外的資料才會被列出來
  • $nor : 與條件都不符合的才會被列出來
  • $or : 結合複數條件,符合其中一個就會被列出來

範例如下

1
2
3
db.movies.find({$or: [{"rating.average": {$lt: 5}}, {"rating.average": {$gt: 9.3}}]}) //rating.average小於5或大於9.3的會被找出來
db.movies.find({$nor: [{"rating.average": {$lt: 5}}, {"rating.average": {$gt: 9.3}}]}) //rating.average不小於5和不大於9.3的會被找出來
db.movies.find({runtime: {$not: {$eq: 60}}}) //runtime不等於60的會被找出來

Element operator

  • $exists: 找出擁有特定field(key)的document
  • $type: 找出特定field是屬於特定資料型態的document

範例如下

1
2
3
4
5
6
db.users.find({age: {$exists: true}}) //有age欄位的document才會被列出來
db.users.find({age: {$exists: true, $gte: 30}}) //有age欄位並且age大於等於30的document會被列出來
db.users.find({age: {$exists: true, $ne: null}}) //有age欄位並且age不是null的document會被列出來

db.users.find({phone: {$type: "number"}}) //phone的資料型態是number的document會被列出來
db.users.find({phone: {$type: ["number","string"]}}) //phone的資料型態是number或string的document會被列出來

Evaluation operator

  • $regex: field包含特定條件的字串的documents會被找出來(使用正規表達式,regular expression)
  • $expr: 同一個document內比較兩個field,並找出比較後回傳的結果
1
2
3
4
5
db.movies.find({summary: {$regex: /musical/}}) //summary只要有包含musical的document就會被列出來

db.sales.find({$expr: {$gt: ["$volume", "$target"]}}) //同一個document內,volume的值大於target的值的話,該document會被列出來
db.sales.find({$expr: {$gt: [{$cond: {if: {$gte: ["$volume", 190]}, then: {$subtract: ["$volume", 30]}, else: "$volume"}}, "$target"]}})
//如果volume的值大於等於190,則將volume-30與target的值比大小,如果沒有大於等於190則直接與target比大小,在上述的前提下volume大於target的document會被列出來

Array

  • $size: 找出陣列中擁有特定item數的document
  • $all: 找出陣列中擁有特定內容的document,陣列內容順序不一樣也可以找出
  • $elemMatch: 找出陣列中的item擁有符合特定條件的field(同個document)
1
2
3
4
5
db.users.find({hobbies: {$size: 3}}) //hobbies陣列裡的item數為3的會被列出來

db.moviestars.find({genre: {$all: ["action", "thriller"]}}) //genre陣列中指要有包含全部指定的內容就會列出來

db.users.find({hobbies: {$elemMatch: {title: "Sports", freqency: {$gte: 3}}}}) //hobbies陣列裡面的item擁有title是Sports、freqency大於等於3的話會被列出來

Cursor

  • 使用find()查找資料時原則上會返回一個指標並且只顯示20筆資料,如果要繼續取得下一個20筆資料shell提供it指令可以繼續向下搜尋
  • 依照應用程式的規模符合條件的資料可能要上千上萬筆,透過資料庫再傳回用戶端很沒效率,因此會使用指標
  • next(): 回傳下一筆資料
  • hasNext(): 檢查是否還有下一筆資料(boolean)
  • sort(): 根據條件排序
  • skip(): 跳過特定數目的資料
  • limit(): 指定cursor回傳的資料數目
1
2
3
4
5
6
7
8
9
10
11
12
13
//由於Mongodb shell是基於javascript執行環境,因此可以使用JS語法
const cursor = db.movies.find()
cursor //這個行為與db.movies.find()相同,會顯示20筆符合的資料
cursor.next() //可以使用next()方法顯示下一筆資料
cursor.forEach(doc => {printjson(doc)}) //可以使用JS語法的forEach對所有符合條件的資料進行操作
cursor.hasNext() //可以使用hasNext方法查看是否還有下一筆資料,執行完上面那行指令後,hasNext()會回傳false

db.movies.find().sort({"rating.average": 1}) //1代表遞增,-1代表遞減
db.movies.find().sort({"rating.average": 1, runtime: -1}) //可以不只限定一個條件,此指令優先依rating.average排序,再對runtime排序
db.movies.find().sort({"rating.average": 1, runtime: -1}).skip(10) //排序完後會跳過10筆,從第11筆開始顯示

db.movies.find().sort({"rating.average": 1, runtime: -1}).skip(10).limit(10) //只會回傳十筆資料
//mongodb會自動照順序執行,以上面的例子來說會先sort、再skip、再limit

Projection

  • 有時我們不需要document中全部的field,這時可以指定回傳的資料只要包含哪些field
1
2
3
4
5
6
7
8
9
10
11
12
13
db.movies.find({},{name: 1, genres: 1, runtime: 1, rating: 1}).pretty()//傳回的資料只會包含指定為1的field,要注意的是_id預設一定會回傳
db.movies.find({},{name: 1, genres: 1, runtime: 1, "schedule.time": 1, rating: 1}).pretty()//已可以指定巢狀裡只要顯示那些field

//genres是一個字串陣列
db.movies.find({genres: "Drama"},{"genres.$": 1}).pretty() //只會顯示找到的第一個genres,也就是Drama(因為指令find要找genre擁有Drama的document)
db.movies.find({genres: {$all: ["Drama","Horror"]}},,{"genres.$": 1}))//就算找到的資料符合都有Drama和Horror,也只會顯示一個

db.movies.find({genres: "Drama"},{genres: {$elemMatch: {$eq: "Horror"}}}) //可以將$elemMatch用在projection,範例結果如下
{ "_id" : ObjectId("5d53c0df9d473c1fa0311998") } //有Drama但是沒有Horror
{ "_id" : ObjectId("5d53c0df9d473c1fa031199a"), "genres" : [ "Horror" ] }//有Drama也有Horror

db.movies.find({"rating.average": {$gt: 9}},{genres: {$slice: 2}, name: 1})//$slice可以指定只要顯示幾個element,此指令會列出所有rating.average大於9的document,但是只會回傳_id, name, 以及genres陣列中的前兩個element
db.movies.find({"rating.average": {$gt: 9}},{genres: {$slice: [1, 2]}, name: 1})//此指令相對於上面的指令,會跳過genres的第一個element,然後接著顯示兩個element

Create

1
2
db."collection名稱".insertOne(data,option) //在指定collection(類似SQL database的table)內插入資料,()內要輸入Json型態的資料
db."collection名稱".insertMany(data,option) //插入多筆documents
  • data為Json格式
  • collection會在insert資料時被動產生,如果需要特殊設定collection的validation可以使用以下指令

mongoDB預設的行為

  • 當我們在插入新資料時可能會遇到一個狀況,就是插入了重複的資料、需要插入的資料已經在資料庫裡了,這時的MongoDB會如下應對
1
2
3
4
5
6
//在下面的範例中,假如_id為cars的document已經存在資料庫,mongoDB預設的行為會在執行到cars時暫停並輸出錯誤,但sports仍會被加到資料庫裡,yoga則不會,因為cars以後就不會被執行了
db.hobbies.insertMany([{_id: "sports", name:"Sports"},{_id: "cars", name:"Cars"},{_id: "yoga", name:"Yoga"}])

//但我們可以更改這個行為,方法如下
db.hobbies.insertMany([{_id: "sports", name:"Sports"},{_id: "cars", name:"Cars"},{_id: "yoga", name:"Yoga"}], {ordered: false})
//假設前提與上面相同,sports被插入到資料庫後,cars會發生錯誤,但是yoga仍會被執行並插入資料庫(ordered預設為true)

Update

1
2
3
4
db."collection名稱".updateOne(filter,data,option) //更新一筆特定的document,db.test.updateOne({test1:"123"},$set:{test2:456})
db."collection名稱".updateMany(filter,data,option) //更新多筆特定的document
db."collection名稱".replaceOne(filter,data,option) //取代某筆特定的document
db."collection名稱".update(filter,data,option) // 可以接受沒有$set,但會直接把資料全部取代成指定的內容,如果要達到相同的效果建議使用replace
  • 如果要操作的對象回複數時,filter設定成 {} 代表對象為全部,delete也適用
  • $set: $set的對象如果存在會更新成指定的值,不存在則會新增該key value
  • $inc: 可以對值進行加減法
  • $mul: 可以對值進行乘法
  • $min: $min在新的值比舊的值小時會更改資料
  • $max: $max在新的值比舊的值大時會更改資料
  • $unset: 指定的field會被drop,也就是該field會變成不存在
  • $rename: 對field進行改名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//_id為5的document中的hobbies會被更改為{title: "Sports", frequency: 5}, {title: "Cooking", frequency: 3}
db.users.updateOne({_id: 5}, {$set: {hobbies: [{title: "Sports", frequency: 5}, {title: "Cooking", frequency: 3}]}})
//所有hobbies中有包含title是Sports的document會被新增一個field isSporty並被設定為true
db.users.updateMany({"hobbies.title": "Sports"}, {$set: {isSporty: true}})
//可以一次新增或更改多個field
db.users.updateOne({_id: 5}, {$set: {age: 30, phone: "123456789"}})

db.users.updateOne({name: "Max"}, {$inc: {age: 1}}) //可以針對特定的field進行加法,此結果age會被加1
db.users.updateOne({name: "Max"}, {$inc: {age: -1}, $set: {isSporty: false}}) //也可以同時使用兩種operator,但如果作用在同一個field會error
db.users.updateOne({name: "Max"}, {$mul: {age: 1.1}}) //$mul會進行乘法,所以Max的age會被乘上1.1

db.users.updateOne({name: "Max"}, {$min: {age: 35}})//如果Max的age大於35則會被更新成35,$min只有在新的值比舊的值小時才會更改資料
db.users.updateOne({name: "Max"}, {$max: {age: 40}})//$max則是在新的值比舊的值還要大時才會更改資料

db.users.updateMany({isSporty: true}, {$unset: {phone: ""}}) //phone後面給予的值並不會改變結果

db.users.updateMany({} {$rename: {age: "totalAge"}}) //field age會被改名成totalAge
  • upsert(): 如果不確定要更新的對象是否存在,如果存在的話需要更新,不存在的話需要新增的話,可以使用此選項
1
2
3
4
5
6
7
8
9
10
11
12
13
//如果name為Maria的document存在的話就會更新,不存在的話就會create,包含搜尋條件name: "Maria"也會被create
db.users.updateOne({name: "Maria"}, {$set: {age: 29, hobbies: [{title: "Good food", frequency: 3}], isSporty: true}}, {upsert: true})
```
### 修改陣列內容

``` javascript
//$符號會自動對應到符合條件的field,所以highFrequency會被新增到符合title是Sports且frequency大於等於3的陣列裡(hobbies是個物件陣列)
db.users.updateMany({hobbies: {$elemMatch: {title: "Sports", frequency: {$gte: 3}}}}, $set: {"hobbies.$.highFrequency": true})
//$[]會更新陣列內所有的element內容,也就是說hobbies內的陣列中,frequency屬性都會被減1
db.users.updateMany({totalAge: {$gt: 30}}, {$inc: {"hobbies.$[].frequency": -1}})
//arrayFilters裡的條件可以跟第一個參數的條件不一樣。找出所有擁有hobbies.frequency大於2的document後,在針對hobbies陣列裡面的hobbies.frequency新增條件,這個條件是hobbies.frequency必須大於2,並且符合這個條件的話該element會被新增goodFrequency這個屬性
db.users.updateMany({"hobbies.frequency": {$gt: 2}}, {$set: {"hobbies.$[el].goodFrequency": true}},
{arrayFilters: [{"el.frequency": {$gt: 2}}]})
  • 增加element
1
2
3
4
5
6
7
8
//使用$push增加一個element到hobbies陣列裡
db.users.updateOne({name: "Maria"}, {$push: {hobbies: {title: "Sports", frequency: 2}}})
//也可以使用$addToSet新增element,與$push不同的是他不能重複新增相同內容的element
db.users.updateOne({name: "Maria"}, {$addToSet: {hobbies: {title: "Sports", frequency: 2}}})
//$each可以增加多個element到陣列裡
db.users.updateOne({name: "Maria"}, {$push: {hobbies: {$each: [{title: "Wine", frequency: 1}, {title: "Hiking", frequency: 2}]}}})
//可以在$each後面在增加排序的方式,如此一來更新時就會按照frequency大的先被插入,而且不只插入的element會被排序,原本在陣列裡的element也會被排序
db.users.updateOne({name: "Maria"}, {$push: {hobbies: {$each: [{title: "Wine", frequency: 1}, {title: "Hiking", frequency: 2}], $sort: {frequency: -1}}}})
  • 移除element
1
2
3
4
//hobbies陣列裡title為Hiking的element會被移除
db.users.updateOne({name: "Maria"}, {$pull: {hobbies: {title: "Hiking"}}})
//使用$pop可以移除陣列第一個或最後一個element,1為最後一個,-1為第一個
db.users.updateOne({name: "Cris"}, {$pop: {hobbies: 1}})

Delete

1
2
db."collection名稱".deleteOne(filter,option) //刪除特定的一筆document,如有兩筆以上符合條件,只會刪除找到的第一筆
db."collection名稱".deleteMany(filter,option)
  • 刪除所有document
1
2
3
db.users.deleteMany({})//會變成空的
db.users.drop()//collections整個被刪除
db.dropDatabase()//必須先使用use進入特定的database

MongoDB資料型態

  • text
  • boolean
  • number(mongo shell是基於javascript,所以預設的數字全部都是float,double的64bit數字)
    • Integer(int32) : 32bit長度的數字 +-2,147,483,647
    • Integer(int64) : 64bit長度的數字 +-9,223,372,036,854,775,807
    • NumberDecimal
  • ObjectId : 插入新資料時mongoDB會自動產生一個唯一的_id,也可以自行設定
  • ISODate
  • Timestamp

使用非預設的數字型態可以使用如下方法

1
db.test.insertOne({a: NumberInt(1)}) //在大部分的情況下與預設型態沒有差別,但可以節省一點空間

限制 : 單一document的大小必須小於等於16mb,document的巢狀結構最多100層

設定Schema的validation

  • 有些時候我們要限制create和update時資料的格式,例如create時一定要包含哪些屬性,且屬性一定要是特定的資料型態,否則會更新失敗或跳出警告訊息,我們使用以下指令來設定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
db.createCollection("collection名稱", {
validator: {
$jsonSchema: {
bsonType: "object",
required:["title", "text", "creator", "comments"], //必須要有這四個屬性
properties:{
title:{
bsonType:"string",
description: "must be a string and required"
},
text:{
bsonType:"string",
description: "must be a string and required"
},
creator:{
bsonType:"objectId"
description: "must be an objectId and required"
},
comments:{
bsonType:"array"
description: "must be a array and required"
items:{
bsonType: "object",
required:["text", "author"],
properties: {
text:{
bsonType:"string",
description: "must be a string and required"
},
aothor:{
bsonType:"objectId"
description: "must be an objectId and required"
}
}
}
}
}
}
}
})
  • 如果要更改設定,可以使用以下指令
1
2
3
4
5
6
7
8
db.runCommand({
collMod: 'posts', //collection的名稱
validator: {
//同上

},
validationAction: 'warn' //就算插入或更新的資料不符合設定,仍可以成功,但警告訊息會被加在log裡(預設為"error")
});

Index

  • MongoDB提供了增加Index的功能,可以幫助在query時提供效率,當進行一個query時,如果沒有index的話MongoDB會查找整個collection來尋找符合條件的資料,但是有了index(ordered),MongoDB可以快速地找到需要的資料,index是一個指標,可以指向他所代表的整個document
  • Index雖然可以增加query的效率,但還是必須付出代價,因為他是一個有排序的資訊,所以每次insert新資料時,其他document都必須更新
  • 要注意的是,如果query所回傳的資料幾乎是全部的document,速度反而會比沒有index還慢,因為index會多一步(透過指標找到整個document的步驟)

讓MongoDB給出更詳細的query資訊

  • explain(): 適用於update、find、delete
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
db.contacts.explain().find({"dob.age": {$gt: 60}})
//回傳
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "contactData.contacts",
"indexFilterSet" : false,
"parsedQuery" : {
"dob.age" : {
"$gt" : 60
}
},
"winningPlan" : {
"stage" : "COLLSCAN", //COLLSCAN代表mongodb掃描了整個collection
"filter" : {
"dob.age" : {
"$gt" : 60
}
},
"direction" : "forward"
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "LAPTOP-FR3O3E10",
"port" : 27017,
"version" : "4.0.11",
"gitVersion" : "417d1a712e9f040d54beca8e4943edce218e9a8c"
},
"ok" : 1
}

db.contacts.explain("executionStats").find({"dob.age": {$gt: 60}})
//回傳會多以下資訊
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 1222, //符合條件的documents
"executionTimeMillis" : 74, //整個query所花的時間(74 milliseconds)
"totalKeysExamined" : 0,
"totalDocsExamined" : 5000, //總共多少doucuments
"executionStages" : {
"stage" : "COLLSCAN",
"filter" : {
"dob.age" : {
"$gt" : 60
}
},
"nReturned" : 1222,
"executionTimeMillisEstimate" : 71,
"works" : 5002,
"advanced" : 1222,
"needTime" : 3779,
"needYield" : 0,
"saveState" : 40,
"restoreState" : 40,
"isEOF" : 1,
"invalidates" : 0,
"direction" : "forward",
"docsExamined" : 5000
}
},
  • createIndex(): 創造index的方法,如此一來被指定的field會代表指標指向不同的documents,並且這些指標是按照順序排列的
  • dropIndex(): 刪掉之前所創造的index,傳的參數跟createIndex一樣
  • getIndexes(): 可以查看現有的index,mongodb會自動以_id作為index。db.contacts.getIndexes()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
db.contacts.createIndex({"dob.age": 1}) //決定要使用index的field,1代表遞增,-1代表遞減
//這時我們再下跟剛剛一樣的query
db.contacts.explain().find({"dob.age": {$gt: 60}})
//回傳
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 1222,
"executionTimeMillis" : 2, //query的速度變快了
"totalKeysExamined" : 1222,
"totalDocsExamined" : 1222,
"executionStages" : {
"stage" : "FETCH",
"nReturned" : 1222,
"executionTimeMillisEstimate" : 0,
"works" : 1223,
"advanced" : 1222,
"needTime" : 0,
"needYield" : 0,
"saveState" : 9,
"restoreState" : 9,
"isEOF" : 1,
"invalidates" : 0,
"docsExamined" : 1222,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN", //使用了Index Scan
"nReturned" : 1222,
"executionTimeMillisEstimate" : 0,
"works" : 1223,
"advanced" : 1222,
//省略
}
}
},

compound index

  • 可不只根據一個field創造index,可以由兩個field組合成一個index
1
2
3
4
5
db.contacts.createIndex({"dob.age": 1, gender: 1}) //會這樣排序: 31 male, 31 female, 32 male, 32 female etc..
//indexName將會是"dob.age_1_gender_1"
//這時我們同時使用dob.age和gender作為query條件時,mongodb會使用index scan,單獨只有dob.age時也會使用index scan
//但當我們只使用第二個也就是gender作為query條件時,mongodb會使用collection scan,因為實際上male並不是由上到下按照順序
//所以假如我們結合了四個field作為一個index,同時使用4個field作為query條件、同時使用前三個、前兩個、或只有第一個時,會使用index scan,但除此之外都會使用collection scan
  • 使用index還有一好處,假如沒有index,當結合sort()時,mongodb會先將所有documents載入記憶體,而這個容量有個上限為32mb,在大部分的狀況下這會造成timeout
1
db.contacts.explain().find({"dob.age": 35}).sort({gender: 1})//就算只有使用age作為query條件,但mongodb最知道你也使用了gender作為sort條件,而使用index

unique index

  • 另一個用處是,可以透過設定第二參數將index設定為unique,藉此防止重複資料的發生,使用insert插入的資料如果unique index有重複就會出現錯誤或警告
  • 要注意的是如果我們有兩筆資料,一個有email另一個沒有email,將email作為index並設為unique會成功,但當insert另一個沒有email的資料時會出現錯誤,因為email為no value重複了(沒有email的資料重複),我們可以利用partial filter來解決
1
db.contacts.createIndex({email: 1}, {unique: true}) //如果email有重複將會出現error

partial filter with index

  • 有時候必須頻繁的查找特定條件的field,就不需要幫整個collection都都加進index的對象裡,這時可以使用partial index
  • unique index提到的no value重複的問題可以用partial index解決
1
2
3
4
5
6
7
8
9
//只有gender為male的document才會根據dob.age為順序產生index
db.contacts.createIndex({"dob.age": 1}, {partialFilterExpression: {gender: "male"}})
//這會進行collection scan,mongodb必須確定你取得所有dob.age大於60的資料,因為dob.age大於60但gender是female的話將不會在index裡
db.contacts.explain().find({"dob.age": {$gt: 60}})
//但如果使用以下的query就會是index scan,而且因為index容量較短,會比compound index或只有index更有效率,而當我們加入gender非male的document時也不會被加入index裡
db.contacts.explain().find({"dob.age": {$gt: 60}, gender: "male"})

//unique index裡的no value重複的解決辦法是結合partial filter,email存在的document才會被加入index
db.users.createIndex({email: 1}, {unique: true, partialFilterExpression: {email: {$exists: true}}})

Time to live(TTL) index

  • TTL index將會設定自動消滅document的時間,適合用在session資料,或是過了期限必須自動消滅的資料
  • 只能用在time data,不能用在compound index。如果使用在非時間的field不會error但會被自動忽略
1
2
3
4
//假設以下的指令按照順序執行
db.sessions.insertOne({data: "sdfsf"}, createAt: new Date())
db.sessions.createIndex({createAt: 1}, {expireAfterSeconds: 10})//雖然已經產生index,但是過了10秒舊有的資料(上面那行insert的資料)還不會被刪除
db.sessions.insertOne({data: "4533"}, createAt: new Date()) //過了10秒後,兩筆資料都會被刪除,insert資料會促使mongodb檢查整個document的TTL index

Multi-key index

  • 如果我們設為index的對象本身是一個array,mongodb將其視為multi-key index
  • multi-key儲存index的方式和一般的index不同,如果一個array有四個elements,這些elements會被另外擷取出來形成index,如果有1000個documents一個document被設為index的field是個array且平均element數為4,index將會有4*1000之多
  • multy-key index可以和一般的index組成coumpound index,但是兩個multi-key不能組成compound index

Text index

  • text index也是multi-key的一種
  • 前面也有介紹搜尋特定的字串時可以使用regular expression($regex)的方式,但是該方式的效能並不是很好,Text index可以提供更好的解決辦法
  • text index會將一段字串針對每個單詞作切割存到text index的array裡,且會自動過濾掉暫停字句(a, the etc.)
  • 所有的大寫字母會被統一轉成小寫字母
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//text index創造的方法有些不同,不是設定1或-1而是"text",如果使用1或-1就會變成一般的index
db.products.createIndex({decription: "text"})
//查找被設為text index的field的內容時,不用指定field,因為一個collection通常只有一個text index(非常expensive)
db.products.find({$text: {$search: "awsome"}}) //description包含awsome的document會被列出來
db.products.find({$text: {$search: "red book"}}) //description包含red或book的document會被列出來
db.products.find({$text: {$search: "\"red book\""}})//如果找兩個詞連在一起的話要用\"包起來
```
- 可以使用projection的方式加入一個新的field來看查找的字串與document的相關度

``` javascript
//score: {$meta: "textScore"}是固定用法
db.products.find({$text: {$search: "awesome t-shirt"}}, {score: {$meta: "textScore"}}).pretty()
//回傳,
"_id" : ObjectId("5d5f78b89d620bff5f141e92"),
"title" : "Red T-Shirt",
"description" : "This T-Shirt is red and it's pretty awesome!",
"score" : 1.7999999999999998 //包含了awesome以及t-shirt所以分數高
}

"_id" : ObjectId("5d5f78b89d620bff5f141e93"),
"title" : "A Book",
"description" : "This is an awesome book about a young artist!",
"score" : 0.625 //只有awesome分數相對低
}

//可以在針對score作排序
db.products.find({$text: {$search: "awesome t-shirt"}}, {score: {$meta: "textScore"}}).sort({score: {$meta: "textScore"}}).pretty()

Combine text index

  • 以上面的products例子來說,如果想將title也設成text index會失敗,因為我們只能有一個text index,但是可以結合text index來解決這個問題
  • 額外說明如果要將text index給刪除我們必須下db.products.dropIndex(“description_text”),而不是dropIndex(description: “text”),”description_text”可以使用getIndexes來查看
  • 不只是可以查找包含哪些字串,還可以找包含特定字串但不包含其他特定字串,可以藉由 - 號來設定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//刪除所有text index後,來創造combine text index
db.products.createIndex({title: "text", decription: "text"})//接著我們查找字串時,如果title也有包含該字串也會被列出來

db.products.find({$text: {$search: "awesome -t-shirt"}})//有awesome但沒有t-shirt的document才會被列出來
```
- 可以針對text index作一些設定

``` javascript
//使用getIndexes()時,可以看到一些field代表一些參數,我們可以設定他
db.products.getIndexes()
//回傳
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "shop.products"
},
{
"v" : 2,
"key" : {
"_fts" : "text",
"_ftsx" : 1
},
"name" : "description_text", //刪除index時可以用這個name
"ns" : "shop.products",
"weights" : { //還可以設定權重,將會影響我們前面用過的textScore
"description" : 1
},
"default_language" : "english", //可以設定
"language_override" : "language",
"textIndexVersion" : 3
}

db.products.dropIndex("description_text") //先把之前的刪除

db.products.createIndex({title: "text", description: "text"}, {default_language: "english", weights: {title: 1, description: 10}})
//這時我們一樣使用projection加入score,會發現分數會根據weights會有變化
//還有一點我們也可以在search的時候加入一些設定,如下
db.products.find($text: {$search: "red", $language: "german"})//document內容可能使用不同的語言
db.products.find($text: {$search: "red", $caseSensitive: true})

Create index in the foreground or background

  • 上面所示範的createIndex都是在foreground執行,這兩者有些差異
    • foreground: index在創造期間collection會被鎖住,不能insert或find等操作,index創造速度較快
    • background: 相對於foreground,collection不會被鎖住,但index創造速度較慢
  • 假設我們同時連線mongodb並創造index,在index還沒完成之前我們插入某一筆資料,這筆資料要一直等到index完成後才會被插入,大型專案的情況下,index可能需要幾分鐘才會被完成,如果在foreground執行會造成整個應用出問題
1
db.rating.createIndex({age: 1}, {background: true})//如此一來就可以在background執行