mongo-05-聚合操作和索引
技術標籤:005-mongo實戰mongodb
1. 聚合操作
db.<collection>.aggregate(<pipeline>,<options>)
<pipeline> 文件定義了操作中使用的聚合管道階段和聚合操作符
<options> 文件聲明瞭一些聚合操作的引數
介紹幾種常見的表示式:
-
欄位路徑表示式
$<field>: 使用 $ 來指示欄位路徑,例如: n a m e 表 示 用 戶 名 字 段 ‘ name 表示使用者名稱欄位 ` name表示用戶名字段‘.` : 使用 $ 和 . 來指示內嵌文件欄位路徑 -
系統變量表達式
$$<variable>: 使用 來 指 示 系 統 變 量 ‘ 來指示系統變數 ` 來指示系統變量‘CURRENT: 指示管道中當前操作的文件$ C U R R E N T . < f i e l d > ‘ 和 ‘ CURRENT.<field>` 和 ` CURRENT.<field>‘和‘` 是等效的 -
常量表達式
$literal: <value>: 指示常量<value>
$literal: "$name": 指示常量字串 “$name”,這裡 $ 被當作常量處理,而不是欄位路徑表示式
聚合管道階段
- $project : 對輸入的文件進行投影
- $match : 對輸入的文件進行過濾篩選
- $limit : 篩選出管道內前N篇文件
- $skip : 跳過管道內前N篇文件
- $unwind : 展開輸入文件中的陣列欄位
- $sort : 對輸入的文件進行排序
- $lookup : 對輸入的文件進行查詢操作
- $group : 對輸入的文件進行分組
- $out : 將管道中的文件輸出
1.1 $project : 對輸入的文件進行投影
$project是一個非常常用的聚合階段,可以用來靈活地控制輸出文件的格式,也可以用來剔除不相關的欄位,以優化聚合管道操作的效能。
現在 user 集合的資料:
> db.user.find()
{ "_id" : "1", "money" : 1500, "name" : "劉一一", "gender" : "女" }
{ "_id" : "2", "money" : 1000, "name" : "陳二", "gender" : "女" }
{ "_id" : "3", "name" : "張三", "sal" : 1000, "gender" : "女" }
{ "_id" : "4", "money" : 800, "name" : "李四", "gender" : "女" }
{ "_id" : "5", "money" : 1000, "name" : "王五", "sal" : 500, "gender" : "女" }
{ "_id" : "6", "name" : "趙六", "money" : 1000 }
{ "_id" : "7", "money" : 1000, "name" : "孫七", "gender" : "女" }
{ "_id" : ObjectId("5fff6a78025e5d9ea86e18cc"), "money" : 1000, "name" : "周八", "gender" : "女" }
{ "_id" : { "age" : 18, "gender" : "男" }, "money" : 1000, "name" : "吳九", "gender" : "女" }
{ "_id" : ObjectId("5fff95783f6d5ed2fcce4133"), "money" : 2000, "name" : "ZHANG", "addr" : [ "三亞", "北京" ], "gender" : "女" }
{ "_id" : ObjectId("60045f5719414c7b5be4c122"), "name" : "富二代", "money" : 1500 }
{ "_id" : ObjectId("6004611c19414c7b5be4c13f"), "name" : "打工人", "money" : 800 }
>
只投影name 和 money 欄位
> db.user.aggregate([{$project: {_id:0, name: 1, money: 1}}])
{ "money" : 1500, "name" : "劉一一" }
{ "money" : 1000, "name" : "陳二" }
{ "name" : "張三" }
{ "money" : 800, "name" : "李四" }
{ "money" : 1000, "name" : "王五" }
{ "name" : "趙六", "money" : 1000 }
{ "money" : 1000, "name" : "孫七" }
{ "money" : 1000, "name" : "周八" }
{ "money" : 1000, "name" : "吳九" }
{ "money" : 2000, "name" : "ZHANG" }
{ "name" : "富二代", "money" : 1500 }
{ "name" : "打工人", "money" : 800 }
>
在返回結果中對投影的欄位設定別名,將 _id 設定別名 “主鍵”
> db.user.aggregate([{$project: {_id:0, name: 1, money: 1, 主鍵: "$_id"}}])
{ "money" : 1500, "name" : "劉一一", "主鍵" : "1" }
{ "money" : 1000, "name" : "陳二", "主鍵" : "2" }
{ "name" : "張三", "主鍵" : "3" }
{ "money" : 800, "name" : "李四", "主鍵" : "4" }
{ "money" : 1000, "name" : "王五", "主鍵" : "5" }
{ "name" : "趙六", "money" : 1000, "主鍵" : "6" }
{ "money" : 1000, "name" : "孫七", "主鍵" : "7" }
{ "money" : 1000, "name" : "周八", "主鍵" : ObjectId("5fff6a78025e5d9ea86e18cc") }
{ "money" : 1000, "name" : "吳九", "主鍵" : { "age" : 18, "gender" : "男" } }
{ "money" : 2000, "name" : "ZHANG", "主鍵" : ObjectId("5fff95783f6d5ed2fcce4133") }
{ "name" : "富二代", "money" : 1500, "主鍵" : ObjectId("60045f5719414c7b5be4c122") }
{ "name" : "打工人", "money" : 800, "主鍵" : ObjectId("6004611c19414c7b5be4c13f") }
>
1.2 $match : 對輸入的文件進行過濾篩選
$match 中使用的文件篩選語法,和讀取文件時的篩選語法相同。
篩選 name=“張三”或者 money=1500 的文件
> db.user.aggregate([{$match: {$or:[{name: "張三"},{money: 1500}]}}])
{ "_id" : "1", "money" : 1500, "name" : "劉一一", "gender" : "女" }
{ "_id" : "3", "name" : "張三", "sal" : 1000, "gender" : "女" }
{ "_id" : ObjectId("60045f5719414c7b5be4c122"), "name" : "富二代", "money" : 1500 }
>
$match 和 $project 結合使用
> db.user.aggregate([{$match: {$or:[{name: "張三"},{money: 1500}]}},{$project: {_id:0, name: 1, money: 1}}])
{ "money" : 1500, "name" : "劉一一" }
{ "name" : "張三" }
{ "name" : "富二代", "money" : 1500 }
>
注意:
$match 也是一個很常用的聚合階段,應該儘量在聚合管道的開始階段應用 $match,這樣可以減少後續階段中需要處理的文件數量,優化聚合操作的效能。
1.3 $limit : 篩選出管道內前N篇文件
篩選第一條文件
> db.user.aggregate([{$limit: 1}])
{ "_id" : "1", "money" : 1500, "name" : "劉一一", "gender" : "女" }
>
1.4 $skip : 跳過管道內前N篇文件
跳過第一條文件
> db.user.aggregate([{$skip: 1}])
{ "_id" : "2", "money" : 1000, "name" : "陳二", "gender" : "女" }
{ "_id" : "3", "name" : "張三", "sal" : 1000, "gender" : "女" }
{ "_id" : "4", "money" : 800, "name" : "李四", "gender" : "女" }
{ "_id" : "5", "money" : 1000, "name" : "王五", "sal" : 500, "gender" : "女" }
{ "_id" : "6", "name" : "趙六", "money" : 1000 }
{ "_id" : "7", "money" : 1000, "name" : "孫七", "gender" : "女" }
{ "_id" : ObjectId("5fff6a78025e5d9ea86e18cc"), "money" : 1000, "name" : "周八", "gender" : "女" }
{ "_id" : { "age" : 18, "gender" : "男" }, "money" : 1000, "name" : "吳九", "gender" : "女" }
{ "_id" : ObjectId("5fff95783f6d5ed2fcce4133"), "money" : 2000, "name" : "ZHANG", "addr" : [ "三亞", "北京" ], "gender" : "女" }
{ "_id" : ObjectId("60045f5719414c7b5be4c122"), "name" : "富二代", "money" : 1500 }
{ "_id" : ObjectId("6004611c19414c7b5be4c13f"), "name" : "打工人", "money" : 800 }
>
1.5 $unwind : 展開輸入文件中的陣列欄位
{$unwind: {path: "$<field>", <includeArrayIndex>: <newFiled>, <preserveNullAndEmptyArrays>:<boolean>}}
path: "$<field>": 指定展開的陣列欄位<includeArrayIndex>: <newFiled>: newField 指定新欄位,用於顯示元素在陣列中的下標,從0開始。<preserveNullAndEmptyArrays>:<boolean>: 預設是false,如果為 true , 展開陣列時保留空陣列或不存在陣列的文件
將文件中的 addr 陣列展開
> db.user.aggregate([{$match: {name: "ZHANG"}}])
{ "_id" : ObjectId("5fff95783f6d5ed2fcce4133"), "money" : 2000, "name" : "ZHANG", "addr" : [ "三亞", "北京" ], "gender" : "女" }
## 1.addr 陣列展開
> db.user.aggregate([{$match: {name: "ZHANG"}},{$unwind: {path: "$addr"}}])
{ "_id" : ObjectId("5fff95783f6d5ed2fcce4133"), "money" : 2000, "name" : "ZHANG", "addr" : "三亞", "gender" : "女" }
{ "_id" : ObjectId("5fff95783f6d5ed2fcce4133"), "money" : 2000, "name" : "ZHANG", "addr" : "北京", "gender" : "女" }
## 1.addr 陣列展開,幷包含新欄位,用於顯示元素下標
> db.user.aggregate([{$match: {name: "ZHANG"}},{$unwind: {path: "$addr",includeArrayIndex: "index"}}])
{ "_id" : ObjectId("5fff95783f6d5ed2fcce4133"), "money" : 2000, "name" : "ZHANG", "addr" : "三亞", "gender" : "女", "index" : NumberLong(0) }
{ "_id" : ObjectId("5fff95783f6d5ed2fcce4133"), "money" : 2000, "name" : "ZHANG", "addr" : "北京", "gender" : "女", "index" : NumberLong(1) }
>
展開陣列時保留空陣列或不存在陣列的文件
## 1.預設是false,不保留空陣列或不存在陣列的文件
> db.user.aggregate([{$unwind: {path: "$addr"}}])
{ "_id" : ObjectId("5fff95783f6d5ed2fcce4133"), "money" : 2000, "name" : "ZHANG", "addr" : "三亞", "gender" : "女" }
{ "_id" : ObjectId("5fff95783f6d5ed2fcce4133"), "money" : 2000, "name" : "ZHANG", "addr" : "北京", "gender" : "女" }
## 1.preserveNullAndEmptyArrays: true 展開陣列時保留空陣列或不存在陣列的文件
> db.user.aggregate([{$unwind: {path: "$addr", preserveNullAndEmptyArrays: true}}])
{ "_id" : "1", "money" : 1500, "name" : "劉一一", "gender" : "女" }
{ "_id" : "2", "money" : 1000, "name" : "陳二", "gender" : "女" }
{ "_id" : "3", "name" : "張三", "sal" : 1000, "gender" : "女" }
{ "_id" : "4", "money" : 800, "name" : "李四", "gender" : "女" }
{ "_id" : "5", "money" : 1000, "name" : "王五", "sal" : 500, "gender" : "女" }
{ "_id" : "6", "name" : "趙六", "money" : 1000 }
{ "_id" : "7", "money" : 1000, "name" : "孫七", "gender" : "女" }
{ "_id" : ObjectId("5fff6a78025e5d9ea86e18cc"), "money" : 1000, "name" : "周八", "gender" : "女" }
{ "_id" : { "age" : 18, "gender" : "男" }, "money" : 1000, "name" : "吳九", "gender" : "女" }
{ "_id" : ObjectId("5fff95783f6d5ed2fcce4133"), "money" : 2000, "name" : "ZHANG", "addr" : "三亞", "gender" : "女" }
{ "_id" : ObjectId("5fff95783f6d5ed2fcce4133"), "money" : 2000, "name" : "ZHANG", "addr" : "北京", "gender" : "女" }
{ "_id" : ObjectId("60045f5719414c7b5be4c122"), "name" : "富二代", "money" : 1500 }
{ "_id" : ObjectId("6004611c19414c7b5be4c13f"), "name" : "打工人", "money" : 800 }
>
1.6 $sort : 對輸入的文件進行排序
$sort 中使用的文件排序語法,和遊標的 sort() 函式排序語法相同
根據money逆向排序和_id正向排序
> db.user.aggregate([{$sort: {money: -1, _id: 1}}])
{ "_id" : ObjectId("5fff95783f6d5ed2fcce4133"), "money" : 2000, "name" : "ZHANG", "addr" : [ "三亞", "北京" ], "gender" : "女" }
{ "_id" : "1", "money" : 1500, "name" : "劉一一", "gender" : "女" }
{ "_id" : ObjectId("60045f5719414c7b5be4c122"), "name" : "富二代", "money" : 1500 }
{ "_id" : "2", "money" : 1000, "name" : "陳二", "gender" : "女" }
{ "_id" : "5", "money" : 1000, "name" : "王五", "sal" : 500, "gender" : "女" }
{ "_id" : "6", "name" : "趙六", "money" : 1000 }
{ "_id" : "7", "money" : 1000, "name" : "孫七", "gender" : "女" }
{ "_id" : { "age" : 18, "gender" : "男" }, "money" : 1000, "name" : "吳九", "gender" : "女" }
{ "_id" : ObjectId("5fff6a78025e5d9ea86e18cc"), "money" : 1000, "name" : "周八", "gender" : "女" }
{ "_id" : "4", "money" : 800, "name" : "李四", "gender" : "女" }
{ "_id" : ObjectId("6004611c19414c7b5be4c13f"), "name" : "打工人", "money" : 800 }
{ "_id" : "3", "name" : "張三", "sal" : 1000, "gender" : "女" }
>
1.7 $group : 對輸入的文件進行分組
{$group: {_id: <expression>, <field1>: {<accumulator1>: <expression1>}...}}
_id: 定義分組規則<field1>: {<accumulator1>: <expression1>}: 可以使用聚合操作符來定義新欄位
根據money進行分組,查詢每組的money總額,平均數,每個分組的數量,最大_id,最小_id
> db.user.aggregate([{$group: {_id: "$money", sum: {$sum: "$money"}, avg: {$avg: "$money"}, count: {$sum: 1}, max_id: {$max: "$_id"}, min_id: {$min: "$_id"}}}])
{ "_id" : 800, "sum" : 1600, "avg" : 800, "count" : 2, "max_id" : ObjectId("6004611c19414c7b5be4c13f"), "min_id" : "4" }
{ "_id" : 1000, "sum" : 6000, "avg" : 1000, "count" : 6, "max_id" : ObjectId("5fff6a78025e5d9ea86e18cc"), "min_id" : "2" }
{ "_id" : 1500, "sum" : 3000, "avg" : 1500, "count" : 2, "max_id" : ObjectId("60045f5719414c7b5be4c122"), "min_id" : "1" }
{ "_id" : 2000, "sum" : 2000, "avg" : 2000, "count" : 1, "max_id" : ObjectId("5fff95783f6d5ed2fcce4133"), "min_id" : ObjectId("5fff95783f6d5ed2fcce4133") }
{ "_id" : null, "sum" : 0, "avg" : null, "count" : 1, "max_id" : "3", "min_id" : "3" }
>
1.8 $out : 將管道中的文件輸出
將聚合管道中的文件寫入一個新的集合 outDoc
## 1.根據money進行分組,查詢每組的money總額,平均數,每個分組的數量,最大_id,最小_id
> db.user.aggregate([{$group: {_id: "$money", sum: {$sum: "$money"}, avg: {$avg: "$money"}, count: {$sum: 1}, max_id: {$max: "$_id"}, min_id: {$min: "$_id"}}}])
{ "_id" : 800, "sum" : 1600, "avg" : 800, "count" : 2, "max_id" : ObjectId("6004611c19414c7b5be4c13f"), "min_id" : "4" }
{ "_id" : 1000, "sum" : 6000, "avg" : 1000, "count" : 6, "max_id" : ObjectId("5fff6a78025e5d9ea86e18cc"), "min_id" : "2" }
{ "_id" : 1500, "sum" : 3000, "avg" : 1500, "count" : 2, "max_id" : ObjectId("60045f5719414c7b5be4c122"), "min_id" : "1" }
{ "_id" : 2000, "sum" : 2000, "avg" : 2000, "count" : 1, "max_id" : ObjectId("5fff95783f6d5ed2fcce4133"), "min_id" : ObjectId("5fff95783f6d5ed2fcce4133") }
{ "_id" : null, "sum" : 0, "avg" : null, "count" : 1, "max_id" : "3", "min_id" : "3" }
## 1.將聚合管道中的文件寫入一個新的集合 outDoc
> db.user.aggregate([{$group: {_id: "$money", sum: {$sum: "$money"}, avg: {$avg: "$money"}, count: {$sum: 1}, max_id: {$max: "$_id"}, min_id: {$min: "$_id"}}},{$out: "outDoc"}])
## 1.檢視新文件 outDoc
> db.outDoc.find()
{ "_id" : 800, "sum" : 1600, "avg" : 800, "count" : 2, "max_id" : ObjectId("6004611c19414c7b5be4c13f"), "min_id" : "4" }
{ "_id" : 1000, "sum" : 6000, "avg" : 1000, "count" : 6, "max_id" : ObjectId("5fff6a78025e5d9ea86e18cc"), "min_id" : "2" }
{ "_id" : 1500, "sum" : 3000, "avg" : 1500, "count" : 2, "max_id" : ObjectId("60045f5719414c7b5be4c122"), "min_id" : "1" }
{ "_id" : 2000, "sum" : 2000, "avg" : 2000, "count" : 1, "max_id" : ObjectId("5fff95783f6d5ed2fcce4133"), "min_id" : ObjectId("5fff95783f6d5ed2fcce4133") }
{ "_id" : null, "sum" : 0, "avg" : null, "count" : 1, "max_id" : "3", "min_id" : "3" }
>
將聚合管道中的文件寫入一個已存在的集合,則會覆蓋已有的集合內容;
如果聚合管道操作遇到錯誤,管道階段不會建立新集合或者覆蓋已存在的集合內容。
1.9. 聚合階段順序
聚合階段執行從前到後的順序:
$match > $sort > $skip > $limit > $project
2. 索引
索引能加快文件查詢和排序的速度。
索引操作
db.collection.getIndexes() db.collection.createIndex() db.collection.dropIndex()
索引的型別
- 單鍵索引
- 複合鍵索引
- 多鍵索引
查詢分析: explain() 檢視索引的效果
索引的特性
- 唯一性
- 稀疏性
- 生存時間
2.1 檢視索引
檢視當前 user 集合的索引,可以看到只有主鍵 _id 的索引,主鍵索引是預設生成的
> db.user.getIndexes()
[ { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_" } ]
>
2.2 建立索引
db.<collection>.createIndex(<keys>,<options>)
<keys>文件: {<field1>:<1 | -1>...} 指定了建立索引的欄位,1 表示升序, -1表示降序。
<options>文件: 定義了建立索引時可以使用的一些引數,也可以設定索引的特性,如 唯一性。
給 money 欄位建立一個單鍵索引
> db.user.createIndex({money: 1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
> db.user.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_"
},
{
"v" : 2,
"key" : {
"money" : 1
},
"name" : "money_1"
}
]
>
為 name 和 money 欄位,建立一個複合鍵索引
> db.user.createIndex({name: 1, money: -1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 2,
"numIndexesAfter" : 3,
"ok" : 1
}
> db.user.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_"
},
{
"v" : 2,
"key" : {
"money" : 1
},
"name" : "money_1"
},
{
"v" : 2,
"key" : {
"name" : 1,
"money" : -1
},
"name" : "name_1_money_-1"
}
]
>
建立一個多鍵索引,陣列 addr 中的每個元素,都會在多鍵索引中建立一個鍵
> db.user.createIndex({addr: 1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 3,
"numIndexesAfter" : 4,
"ok" : 1
}
>
> db.user.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_"
},
{
"v" : 2,
"key" : {
"money" : 1
},
"name" : "money_1"
},
{
"v" : 2,
"key" : {
"name" : 1,
"money" : -1
},
"name" : "name_1_money_-1"
},
{
"v" : 2,
"key" : {
"addr" : 1
},
"name" : "addr_1"
}
]
>
2.3 刪除索引
db.<collection>.dropIndex("indexName") 或者 db.<collection>.dropIndex({<field1>:<1 | -1>...})
- 根據"索引 name"刪除索引
- 根據"索引 key"刪除索引
如果需要更改某些欄位上已經建立的索引,必須先刪除原有的索引,再重新建立新索引,否則,新索引不會包含原有的文件
根據"索引 name"刪除索引:
> db.user.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_"
},
{
"v" : 2,
"key" : {
"money" : 1
},
"name" : "money_1"
},
{
"v" : 2,
"key" : {
"name" : 1,
"money" : -1
},
"name" : "name_1_money_-1"
},
{
"v" : 2,
"key" : {
"addr" : 1
},
"name" : "addr_1"
}
]
# 刪除索引 "addr_1"
> db.user.dropIndex("addr_1")
{ "nIndexesWas" : 4, "ok" : 1 }
# 刪除索引 "money_1"
> db.user.dropIndex("money_1")
{ "nIndexesWas" : 3, "ok" : 1 }
> db.user.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_"
},
{
"v" : 2,
"key" : {
"name" : 1,
"money" : -1
},
"name" : "name_1_money_-1"
}
]
>
根據"索引 key"刪除索引:
> db.user.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_"
},
{
"v" : 2,
"key" : {
"name" : 1,
"money" : -1
},
"name" : "name_1_money_-1"
}
]
# 根據"索引 key"刪除索引
> db.user.dropIndex({name:1,money:-1})
{ "nIndexesWas" : 2, "ok" : 1 }
> db.user.getIndexes()
[ { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_" } ]
2.4 建立索引 options
db.<collection>.createIndex(<keys>,<options>)
<options>文件: 定義了建立索引時可以使用的一些引數,也可以設定索引的特性,如 唯一性、稀疏性。
複合鍵索引也可以具有唯一性,在這種情況下,不同的文件之間,其所包含的複合鍵欄位值的組合不可以重複。
當前 user 集合只有主鍵索引
> db.user.getIndexes()
[ { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_" } ]
>
2.4.1 索引的唯一性
建立一個具有唯一性的索引,給 name 欄位建立唯一索引
> db.user.createIndex({name: 1},{unique: true})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
> db.user.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_"
},
{
"v" : 2,
"unique" : true,
"key" : {
"name" : 1
},
"name" : "name_1"
}
]
>
user 集合中文件
> db.user.find()
{ "_id" : "1", "money" : 1500, "name" : "劉一一", "gender" : "女" }
{ "_id" : "2", "money" : 1000, "name" : "陳二", "gender" : "女" }
{ "_id" : "3", "name" : "張三", "sal" : 1000, "gender" : "女" }
{ "_id" : "4", "money" : 800, "name" : "李四", "gender" : "女" }
{ "_id" : "5", "money" : 1000, "name" : "王五", "sal" : 500, "gender" : "女" }
{ "_id" : "6", "name" : "趙六", "money" : 1000 }
{ "_id" : "7", "money" : 1000, "name" : "孫七", "gender" : "女" }
{ "_id" : ObjectId("5fff6a78025e5d9ea86e18cc"), "money" : 1000, "name" : "周八", "gender" : "女" }
{ "_id" : { "age" : 18, "gender" : "男" }, "money" : 1000, "name" : "吳九", "gender" : "女" }
{ "_id" : ObjectId("5fff95783f6d5ed2fcce4133"), "money" : 2000, "name" : "ZHANG", "addr" : [ "三亞", "北京" ], "gender" : "女" }
{ "_id" : ObjectId("60045f5719414c7b5be4c122"), "name" : "富二代", "money" : 1500 }
{ "_id" : ObjectId("6004611c19414c7b5be4c13f"), "name" : "打工人", "money" : 800 }
>
已經存在 name=張三 的文件,因為 name 欄位具有唯一索引,如果再建立一個 name=張三 的文件會報錯
> db.user.insert({name: "張三", money: 1800})
WriteResult({
"nInserted" : 0,
"writeError" : {
"code" : 11000,
"errmsg" : "E11000 duplicate key error collection: test.user index: name_1 dup key: { name: \"張三\" }"
}
})
>
如果新增的文件不包含唯一索引欄位,只有第一個缺失該欄位的文件可以被寫入資料庫,索引中該文件的鍵值被預設為null。
# 第一個文件,name欄位不存在,則預設為null
> db.user.insert({money: 1100})
WriteResult({ "nInserted" : 1 })
> db.user.find({money: 1100})
{ "_id" : ObjectId("6004c82e21bca04a14f52bee"), "money" : 1100 }
# 第二個文件,name欄位不存在,建立時報錯
> db.user.insert({money: 1200})
WriteResult({
"nInserted" : 0,
"writeError" : {
"code" : 11000,
"errmsg" : "E11000 duplicate key error collection: test.user index: name_1 dup key: { name: null }"
}
})
2.4.2 索引的稀疏性
只將包含索引鍵欄位的文件加入到索引中,索引鍵欄位值為null也會被加入索引,但是如果欄位不存在,則不加入。
如果同一個索引既具有唯一性,又具有稀疏性,就可以儲存多篇缺失索引鍵值的文件了。
重新給 name 欄位建立唯一稀疏索引,因為已經存在 name 的唯一索引,因此要先刪除,再重新建立
> db.user.createIndex({name: 1},{unique:true,sparse:true})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
> db.user.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_"
},
{
"v" : 2,
"unique" : true,
"key" : {
"name" : 1
},
"name" : "name_1",
"sparse" : true
}
]
現在已經存在一個name不存在的文件
> db.user.find({name: {$exists: false}})
{ "_id" : ObjectId("6004c82e21bca04a14f52bee"), "money" : 1100 }
>
再插入一個name不存在的文件,插入成功
> db.user.insert({money: 1200})
WriteResult({ "nInserted" : 1 })
> db.user.find({name: {$exists: false}})
{ "_id" : ObjectId("6004c82e21bca04a14f52bee"), "money" : 1100 }
{ "_id" : ObjectId("6004ce5521bca04a14f52bf0"), "money" : 1200 }
>
2.5 explain() 查詢分析
db.<collection>.explain(<verbose>).<method(...)>
-
<verbose>: {String},可選引數。指定冗長模式的解釋輸出,方式指定後會影響explain()的行為及輸出資訊。可選值有:“queryPlanner”、“executionStats”、“allPlansExecution”,預設為"queryPlanner" -
queryPlanner :查詢計劃,查詢優化選擇的計劃細節和被拒絕的計劃,預設會顯示。
-
executionStats : 執行狀態,會返回最佳執行計劃的一些統計資訊
-
allPlansExecution : 用來獲取所有執行計劃
可以使用 explain() 進行分析的命令包括 aggregate(),count(),distinct(),find(),group(),remove(),update()
2.5.1 explain 返回資訊
queryPlanner(查詢計劃):查詢優化選擇的計劃細節和被拒絕的計劃。其可能包括以下值:
namespace :查詢的集合
indexFilterSet:一個Boolean值,表示MongoDB在查詢中是否使用索引過濾
winningPlan :最佳執行計劃
shards : 包括每個訪問片的queryPlanner和serverInfo的文件陣列
stage : 查詢方式
filter : 過濾條件
direction : 查詢順序
inputStage : 用來描述子stage,並且為其父stage提供文件和索引關鍵字
keyPattern : 所掃描的index key
indexName : 索引名
isMultiKey : 是否是Multikey。如果索引建立在array上,將是true
inputStages : 表示子過程的文件陣列
rejectedPlans : 被查詢優化備選並被拒絕的計劃陣列
executionStats,(執行狀態):被選中執行計劃和被拒絕執行計劃的詳細說明:
executionSuccess : 是否執行成功
nReturned : 返回的個數
executionTimeMillis : 計劃選擇和查詢執行所需的總時間(毫秒數)
totalKeysExamined : 掃描的索引總數
totalDocsExamined : 掃描的文件總數
executionStages : 顯示執行成功細節的查詢階段樹
executionTimeMillisEstimate : 檢索文件獲取資料的時間
works : 指定查詢執行階段執行的“工作單元”的數量
advanced : 優先返回的結果數
needTime : 未將中間結果推進到其父級的工作週期數
needYield : 儲存層要求查詢系統產生的鎖的次數
isEOF : 指定執行階段是否到達 steam 結尾,1 或者 true 代表已到達結尾
docsExamined : 文件檢查數
executionStats.allPlansExecution : 包含在計劃選擇階段期間捕獲的部分執行資訊,包括選擇計劃和拒絕計劃
serverInfo(伺服器資訊):MongoDB例項的相關資訊:
stage狀態
COLLSCAN: 全表掃描IXSCAN: 索引掃描FETCH: 根據索引檢索指定文件SHARD_MERGE: 將各個分片返回資料進行合併SORT: 在記憶體中進行了排序LIMIT: 使用limit限制返回數SKIP: 使用skip進行跳過IDHACK: 對_id進行查詢SHARDING_FILTER: 通過mongos對分片資料進行查詢COUNTSCAN: count不使用Index進行count時的stage返回COUNT_SCAN: count使用了Index進行count時的stage返回SUBPLA: 未使用到索引的$or查詢的stage返回TEXT: 使用全文索引進行查詢時候的stage返回PROJECTION: 限定返回欄位時候stage的返回
執行計劃的返回結果中儘量不要出現以下stage
COLLSCAN: 全表掃描SORT: (使用sort但是無index)SKIP: 不合理的SKIPCOUNTSCAN: count不使用Index進行count時的stage返回SUBPLA: 未使用到索引的$or查詢的stage返回
使用沒有建立索引的欄位進行搜尋
> db.user.explain("executionStats").find({money: 1000})
{
"queryPlanner" : {
"plannerVersion" : 1,
# 查詢的集合
"namespace" : "test.user",
# MongoDB在查詢中是否使用索引過濾
"indexFilterSet" : false,
# 查詢條件
"parsedQuery" : {
"money" : {
"$eq" : 1000
}
},
"queryHash" : "44936D04",
"planCacheKey" : "44936D04",
# 最佳執行計劃
"winningPlan" : {
# 查詢方式:COLLSCAN 全表掃描
"stage" : "COLLSCAN",
#過濾條件
"filter" : {
"money" : {
"$eq" : 1000
}
},
# 查詢順序
"direction" : "forward"
},
"rejectedPlans" : [ ]
},
# 執行狀態
"executionStats" : {
# 執行成功
"executionSuccess" : true,
# 返回的個數
"nReturned" : 6,
# 計劃選擇和查詢執行所需的總時間(毫秒數)
"executionTimeMillis" : 0,
# 掃描的索引總數
"totalKeysExamined" : 0,
# 掃描的文件總數
"totalDocsExamined" : 15,
"executionStages" : {
# 查詢方式:COLLSCAN 全表掃描
"stage" : "COLLSCAN",
# 過濾條件
"filter" : {
"money" : {
"$eq" : 1000
}
},
# 返回的個數
"nReturned" : 6,
# 檢索文件獲取資料的時間
"executionTimeMillisEstimate" : 0,
"works" : 17,
"advanced" : 6,
"needTime" : 10,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"direction" : "forward",
# 文件檢查數
"docsExamined" : 15
}
},
# Mongo伺服器資訊
"serverInfo" : {
"host" : "docker01",
"port" : 27017,
"version" : "4.4.3",
"gitVersion" : "913d6b62acfbb344dde1b116f4161360acd8fd13"
},
"ok" : 1
}
>
使用已經建立索引的欄位進行搜尋
> db.user.explain("executionStats").find({name: "張三"})
{
"queryPlanner" : {
"plannerVersion" : 1,
# 查詢集合
"namespace" : "test.user",
# 在查詢中是否使用索引過濾
"indexFilterSet" : false,
# 查詢條件
"parsedQuery" : {
"name" : {
"$eq" : "張三"
}
},
"queryHash" : "01AEE5EC",
"planCacheKey" : "4C5AEA2C",
# 最佳執行計劃
"winningPlan" : {
# 查詢方式:FETCH 根據索引檢索指定文件
"stage" : "FETCH",
# 用來描述子stage,並且為其父stage提供文件和索引關鍵字
"inputStage" : {
# 查詢方式:IXSCAN 索引掃描
"stage" : "IXSCAN",
# 所掃描的index key
"keyPattern" : {
"name" : 1
},
# 索引名稱
"indexName" : "name_1",
# 不是多鍵索引
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : [ ]
},
# 唯一索引
"isUnique" : true,
# 稀疏索引
"isSparse" : true,
"isPartial" : false,
"indexVersion" : 2,
# 查詢順序
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"張三\", \"張三\"]"
]
}
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
# 執行成功
"executionSuccess" : true,
# 返回結果數
"nReturned" : 1,
# 執行時間
"executionTimeMillis" : 0,
# 掃描索引數
"totalKeysExamined" : 1,
# 掃描文件數
"totalDocsExamined" : 1,
"executionStages" : {
# 查詢方式:FETCH 根據索引檢索指定文件
"stage" : "FETCH",
# 返回結果數
"nReturned" : 1,
# 執行時間
"executionTimeMillisEstimate" : 0,
"works" : 2,
"advanced" : 1,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
# 文件檢查
"docsExamined" : 1,
"alreadyHasObj" : 0,
"inputStage" : {
# 查詢方式:索引掃描
"stage" : "IXSCAN",
"nReturned" : 1,
"executionTimeMillisEstimate" : 0,
"works" : 2,
"advanced" : 1,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
# 索引 key
"keyPattern" : {
"name" : 1
},
# 索引名稱
"indexName" : "name_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : [ ]
},
"isUnique" : true,
"isSparse" : true,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"張三\", \"張三\"]"
]
},
"keysExamined" : 1,
"seeks" : 1,
"dupsTested" : 0,
"dupsDropped" : 0
}
}
},
"serverInfo" : {
"host" : "docker01",
"port" : 27017,
"version" : "4.4.3",
"gitVersion" : "913d6b62acfbb344dde1b116f4161360acd8fd13"
},
"ok" : 1
}
>
僅返回建立了索引的欄位
> db.user.explain().find({name: "張三"},{_id:0,name:1})
{
"queryPlanner" : {
"plannerVersion" : 1,
# 查詢集合
"namespace" : "test.user",
# 在查詢中是否使用索引過濾
"indexFilterSet" : false,
# 查詢條件
"parsedQuery" : {
"name" : {
"$eq" : "張三"
}
},
"queryHash" : "3066FB64",
"planCacheKey" : "A5386A93",
# 最佳執行計劃
"winningPlan" : {
# 查詢方式:PROJECTION_COVERED 索引覆蓋查詢
"stage" : "PROJECTION_COVERED",
"transformBy" : {
"_id" : 0,
"name" : 1
},
# 用來描述子stage,並且為其父stage提供文件和索引關鍵字
"inputStage" : {
# 查詢方式:IXSCAN 索引掃描
"stage" : "IXSCAN",
# 所掃描的index key
"keyPattern" : {
"name" : 1
},
# 索引name
"indexName" : "name_1",
# 不是多鍵索引
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : [ ]
},
# 唯一索引
"isUnique" : true,
# 稀疏索引
"isSparse" : true,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"張三\", \"張三\"]"
]
}
}
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "docker01",
"port" : 27017,
"version" : "4.4.3",
"gitVersion" : "913d6b62acfbb344dde1b116f4161360acd8fd13"
},
"ok" : 1
}
>
使用未建立索引的欄位進行排序
> db.user.explain().find().sort({money:1})
{
"queryPlanner" : {
"plannerVersion" : 1,
# 查詢集合
"namespace" : "test.user",
# 在查詢中是否使用索引過濾
"indexFilterSet" : false,
# 查詢條件
"parsedQuery" : {
},
"queryHash" : "1B95C3FD",
"planCacheKey" : "1B95C3FD",
# 最佳執行計劃
"winningPlan" : {
# 查詢方式:SORT 在記憶體中進行了排序
"stage" : "SORT",
"sortPattern" : {
"money" : 1
},
"memLimit" : 104857600,
"type" : "simple",
# 用來描述子stage,並且為其父stage提供文件和索引關鍵字
"inputStage" : {
# 查詢方式:COLLSCAN 全表掃描
"stage" : "COLLSCAN",
"direction" : "forward"
}
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "docker01",
"port" : 27017,
"version" : "4.4.3",
"gitVersion" : "913d6b62acfbb344dde1b116f4161360acd8fd13"
},
"ok" : 1
}
>
使用已經建立索引的欄位進行排序
# 給 money 建立一個正序的索引
> db.user.createIndex({money:1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 2,
"numIndexesAfter" : 3,
"ok" : 1
}
# 對使用已經建立索引的欄位進行排序進行分析
> db.user.explain().find().sort({money:1})
{
"queryPlanner" : {
"plannerVersion" : 1,
# 查詢集合
"namespace" : "test.user",
# 在查詢中是否使用索引過濾
"indexFilterSet" : false,
# 查詢條件
"parsedQuery" : {
},
"queryHash" : "1B95C3FD",
"planCacheKey" : "1B95C3FD",
# 最佳執行計劃
"winningPlan" : {
# 查詢方式:FETCH 根據索引檢索指定文件
"stage" : "FETCH",
# 用來描述子stage,並且為其父stage提供文件和索引關鍵字
"inputStage" : {
# 查詢方式:IXSCAN 索引掃描
"stage" : "IXSCAN",
# 所掃描的index key
"keyPattern" : {
"money" : 1
},
# 索引名稱
"indexName" : "money_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"money" : [ ]
},
# 非唯一索引
"isUnique" : false,
# 非稀疏索引
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"money" : [
"[MinKey, MaxKey]"
]
}
}
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "docker01",
"port" : 27017,
"version" : "4.4.3",
"gitVersion" : "913d6b62acfbb344dde1b116f4161360acd8fd13"
},
"ok" : 1
}
>
[慕課手記同步:mongo-05-聚合操作和索引] https://www.imooc.com/article/314463
歡迎關注文章同步公眾號"黑桃"

