-
三種更新方法:
1. update(將要廢棄,可跳過,直接看2,3點的方法)
update({查詢條件}, {更新操做符} , {更新選項})
M: table.update({'name': {'$regex':'li'}},{'$set':{'name':'lin2'}}, {multi: true})
P: table.update({'name': {'$regex': 'li'}}, {'$set': {'name': 'lin3'}},multi=True)
注意1: 第三個參數 multi若是不設置,默認只更新一條文檔,設置爲 true ,就會更新多條文檔
注意2:
Mongo寫法: {multi: true} # Mongo 和往常同樣,採用json格式, true小寫
Python寫法: multi = True # python是採用命名參數來傳遞, True大寫
2. updateOne(更新一條)
M: updateOne( {查詢條件}, {更新操做符} )
P: update_one
3. updateMany(更新多條)
M: updateMany( {查詢條件}, {更新操做符} ) 其實參數是如出一轍的,只不過方法名區分
P: update_many
注: 這三個方法的參數 是基本如出一轍的
因此下面講具體 {查詢條件}, {更新操做符} 時
就統一用 update()來寫了
普通更新操做符:
-
$set(更新)
# 注:規則就是:"有則改之, 無則添加"
M: table.update({'5':5},{'$set': {'lin': [5,6,7,8]} })
P: 同上
微擴展(關於內嵌數組):
table.update({'5':5},{'$set': {'lin.0': '呵呵' }) # lin.0表明數組的第一個元素
當數組的索引越界,這個時候就視爲數組的添加操做。
eg: 假定咱們給 lin.10 一個值,那麼 中間空出的那麼多索引,會自動填充 null
-
$unset(刪除)
# 注:刪除的鍵對應的value能夠隨便寫,寫啥都會刪除, 寫 '' 只是爲了語義明確(規範)
M: table.update({'6':6}, {'$unset': {'6':''}}) # 把此條記錄的 '6' 字段刪除
P: 同上
微擴展(關於嵌套數組):
table.update({'5':5}, {'$unset': {'lin.0':''}}) # lin.0一樣表明數組第一個元素
注:數組的刪除 並非真正的刪除, 而是把值 用 null 替換
-
$rename(更名,替換)
M: table.update({'name':'lin'}, {'$rename':{'name':'nick'}}) # name變成了nick
P: 同上
微擴展(文檔嵌套):
若是文檔是嵌套的 eg: { a: {b:c} }
M: table.update({'lin':'lin'}, {'$rename': {'a.b':'d'}})
P: 同上
結果 => {"a" : { }, "d" : "c" }
解析:
b 屬於 子文檔
a.b 表示 經過父文檔的a 來取出 子文檔的b
若是總體a.b被 rename爲 d,那麼 d會被安排到父文檔的層級裏,而a設爲空。
舉個栗子:
你有一個箱子,裏面 有一個 兒子級別 和 孫子級別 的箱子 (共3層)
如今你把 孫子級別的箱子 單獨拿出來, 把整個箱子替換掉
就是這種思想。。。本身體會吧
(這種語法,好像Python列表的切片賦值。。形容可能不太恰當)
-
$inc:
{$inc: { 'age': -2}} # 減小兩歲,正數表示加法,負數表示減法,簡單,不舉例了
特例:若是字段不存在,那麼,此字段會被添加, 而且值就是你設定的值(0+n=n)
-
$mul:
{$mul: { 'age': 0.5}} # 年齡除以2,整數表示乘法,小數表示除法,簡單,不舉例了
特例:若是字段不存在,那麼,此字段會被添加, 而且值爲0 (0*n=0)
-
$min
{$min: { 'age': 30}} # 30比原有值小:就替換, 30比原有值大,則不作任何操做
-
$max
{$max: { 'age': 30}} # 30比原有值大:就替換, 30比原有值小,則不作任何操做
特例:min和max特例相同,即若是字段不存在,那麼,此字段會被添加, 而且值就是你設定的值
-
數組更新操做符:
"""
單數組: xx
內嵌數組: xx.索引
"""
-
$addToSet(有序,無重複,尾部添加)
原始數據: {'1':1}
M: table.update({'1':1}, {'$addToSet':{'lin':[7,8]}})
P: 同上
結果 => {"1": 1,"lin": [ [7, 8 ] ]} # [7,8] 總體插入進來, 特別注意這是二級列表
-
$each ( 給[7,8]加個 $each,注意看結果變化 )
M: table.update({'1': 1}, {'$addToSet': {'lin': {'$each':[7, 8]} }})
P: 同上
結果 => {"1": 1, "lin": [7,8]} # 7,8單獨插入進來,參考python的 * 解構
-
$push(數據添加, 比$addToSet強大,可任意位置,可重複)
"""
補充說明:
$addToSet:添加數據有重複,會自動去重
$push :添加數據有重複,不會去重,而是直接追加
"""
原始數據: {'1':1}
M: table.update(
{ '1': 1 },
{
'$push': {
'lin': {
'$each': [ {'a': 5, 'b': 8 }, { 'a': 6, 'b': 7 }, {'a': 7, 'b': 6 } ],
'$sort': { 'a': -1 },
'$position': 0,
'$slice': 2
}}}) # 這裏爲了清晰點,我就把全部括號摺疊起來了
P: 同上
結果 => {"1" : 1, "lin" : [ { "a" : 7, "b" : 6 }, { "a" : 6, "b" : 7 } ] }
終極解析:
1. 添加數組: 先走 $sort => 根據a 逆序排列
2. 再走 $position, 0表示:索引定位從0開始
3. 再走 $slice, 2表示: 取2個
4. 最後走 $each,把數組元素逐個放進另外一個數組,說過的,至關於python的 * 解構操做,
-
$pop(只能 刪除 頭或尾 元素)
M: table.update({'a': a}, {'$pop': {'lin': 1}}) # 刪除最後一個
P: 同上
注1:$pop參數, 1表明最後一個, -1表明第一個。 這個是值得注意一下的,容易記反
注2:若是所有刪沒了,那麼會剩下空[], 而不是完全刪除字段
-
$pull (刪除 任何位置 的 指定的元素)
M: table.update({'1': 1},{'$pull':{ 'lin':[7,8]}}) # 刪除數組中[7,8]這個內嵌數組
P: 同上
-
$pullAll(基本和 $pull 一致)
M: table.update({'1': 1},{'$pullAll':{ 'lin':[ [7,8] ]}}) # 同$pull,但多了個 []
P: 同上
注: $pull 和 $pullAll 針對於 內嵌文檔 和 內嵌數組 有細小差異, 差異以下:
內嵌數組:
$pull 和 $pullAll 都嚴格要求內嵌數組的 排列順序,順序不一致,則不返回
內嵌文檔:
$pullAll : 嚴格要求內嵌文檔的順序, 順序不一致,則 不返回
$pull : 不要求內嵌文檔的循序, 順序不一致,同樣能夠返回
-
投影解釋
哪一個字段 設置爲 0, 此字段就不會被投影, 而其餘字段所有被投影
哪一個字段 設置爲 1, 此字段就會被單獨投影, 其餘字段不投影
{'name': 0, 'age': 0} # 除了 name 和 age ,其餘字段 都 投影
{'name': 1, 'age': 1} # 只投影 name 和 age, 其餘字段 不 投影,(_id除外)
注意:全部字段必須知足以下要求:
一: 你能夠不設置,默認都會被投影
二: 若是你設置了,就必須同爲0,或者同爲1,不容許0,1 混合設置(_id除外)
三: _id雖然能夠參與混合設置,可是它只能夠設爲0, 不能夠設爲1,由於1是它默認的
通俗理解(0和1的設定):另外一種理解思想 ====>
設置爲1: 就是 加入 白名單 機制
設置爲0, 就是 加入 黑名單 機制
注: _id字段是 MongoDB的默認字段,它是會一直被投影的(默認白名單)
可是,當你強制指定 {'_id': 0} ,強制把 _id指定爲0,他就不會被投影了(變爲黑名單)
語法:
M: queryset = table.find({}, {'name': 0})
P: 同上
-
投影-數組切片($slice)
"""針對投影時的value爲數組的狀況下,對此數組切片,而後再投影"""
數據條件: {'arr1': [5,6,7,8,9] }
整形參數:
M: queryset = table.find({},{'arr1':{'$slice': 2}}) # 2表示前2個, -2表示後兩個
P: 同上,如出一轍,一字不差
結果: { 'arr1': [5,6] }
數組參數: [skip, limit]
M: queryset = table.find({},{'arr1':{'$slice': [2,3]}}) # 跳過前2個,取3個
P: 同上,如出一轍,一字不差
輸出結果 => { 'arr1': {7,8,9] }
注: 這種數組參數,你能夠用 skip+limit 方式理解
也能夠用, python的索引+切片方式理解 (skip開始查索引(0開始數), 而後取limit個)
-
投影-數組過濾($elemMatch)
"""
針對投影時 的value爲數組的狀況下,根據指定條件 對 數組 過濾,而後再投影
注意這個過濾機制: 從前向後找,遇到一個符合條件的就馬上投影(相似 python正則的 search)
"""
數據條件: {'arr1': [6,7,8,9]}
M: queryset = table.find({}, {'arr1': {'$elemMatch': {'$gt':5}} })
P: 同上
輸出結果 => "arr1" : [ 6 ]
解析:(我本身總結的僞流程,可參考理解)
1. 準備投影
2. 發現數組,先處理數組,可看到數組中有 elemMatch條件
elemMatch在投影中定義爲:
」你給我一個條件,我把符合條件的 數組每一個元素從前向後篩選
遇到第一個符合條件的就返回, 剩下的都扔掉 (這裏的返回你能夠理解爲 return)
「
3. 把 2 步驟 返回的數據 投影
-
limit()
limit: (只取前n條)
M: queryset = table.find({'name':'lin'}).limit(n) # n就是取的條數
P: 同上
-
skip()
skip: (跳過n條,從第n+1條開始取)
M: queryset = table.find({'name':'lin'}).skip(n) # 從0開始數
P: 同上
解釋一下skip這個參數n:
假如n等於2 ,就是從第三個(真實個數)開始取 => 你能夠借鑑數組索引的思想 a[2]
-
count()
count: (統計記錄數)
M: count_num = table.find({'name':'lin'}).skip(1).limit(1).count()
P: count_num = table.count_documents(filter={'name':'lin'}, skip=1, limit=1)
分析:
find() -> 查出 3 條數據
skip(1) -> 跳過一條,就是從第二條開始取
limit(1) -> 接着上面的來,從第二條開始取(算自己哦),取一個,實際上取的就是第二條
count() -> 3 # 也許你很驚訝,按常理來講,結果應該爲 1(看下面)
count(applySkipLimit=false) # 這是 API原型,這個參數默認爲False
applySkipLimit: 看名字你就知道這函數做用了吧
默認不寫爲 False: 不該用(忽略) skip(), limit() 來統計結果 ==> 上例結果爲 3
設爲 True: 結合 skip(), limit() 來統計最終結果 ==> 上例結果爲 1
注: 對於 count() ,Mongo 和 PyMongo都有此方法,且用法是如出一轍的。
那爲何上面PyMongo中我卻用了 count_documents() 而不是 count() ?????
答:
由於 運行 或者後 戳進PyMongo源碼可清晰看見,將來版本 count() API將要廢除。
官方建議咱們用 count_documents()
它的好處是把 skip() 和 limit() 由兩個函數調用 變爲 2個參數傳進去了。
-
sort()
sort: 排序
M: queryset = table.find({'name':'lin'}).sort({'_id': -1}) # 注意,參數是{} 對象
P: queryset = table.find({'name':'lin'}).sort( '_id', -1 ) # 注意,這是2個參數
第一個參數,表明 排序依據的字段屬性
第二個參數,表明 升/降
1 : 升序 eg: 456
-1: 降序 eg: 654
特別注意: 3連招順序(優先級要牢記) ()
sort -> skip -> limit (排序 - 定位 - 挑選) 不管你代碼什麼順序,它都會這個順序執行
eg: queryset = table.find({'name': 'lin'}).sort('_id', -1).skip(1).limit(1)
也許你會有這樣一個疑惑: 爲何 count_documents 沒有放進連招裏面?
答:
你仔細想一想, 統計個數,和你排不排序有關係嗎?
沒錯,一點關係都沒有。。。 sort() 和 count() 沒有聯繫
-
數組操做符
已有數據條件: { name: ['張','李','王'] }
$all:
M: queryset = table.find({'name': {'$all': ['張','李']}}) # 數組值裏必須包含 張和李
P:同上,如出一轍,一字不差
$elemMatch:
M: queryset = table.find({'name': {'$elemMatch': {'$eq':'張'} }}) # 數組值有張 就行
P: 同上,如出一轍,一字不差
-
正則
M: db.xx.find( {name: { $regex: /^a/, $options:'i' }} )
P: queryset = db.xx.find({'name': {'$regex': 'LIN', '$options': 'i'}})
PyMongo版的或者這樣寫->
import re
e1 = re.compile(r'LIN', re.I) # 把Python的正則對象 代替 Mongo語句
queryset = db.xx.find({'name': {'$regex': re1 }})
聚合表達式
-
字段路徑表達式:
$name # 具體字段
-
系統變量表達式:
$$CURRENT # 表示管道中,當前操做的文檔
-
反轉義表達式:
$literal: '$name' # 此處 $name 原語法被破壞,如今它只是單純的字符串
-
聚合管道
"""
單個管道,就像 Python中的 map等高階函數原理, 分而治之。
只不過,MongoDB善於將管道串聯而已。
.aggregate([ 裏面寫管道各類操做 ])
"""
-
$match(管道查詢)
M: queryset = table.aggregate([{'$match': {'name': 'zhangsan'}}])
P: 同上
-
$project(管道投影)
數據條件 =>
[
{"id":'xxx', "name" : "zhangsan", "age" : 15 },
{"id":'xxx', "name" : "lisi", "age" : 18 },
{"id":'xxx', "name" : "wangwu", "age" : 16 }
]
M: queryset = table.aggregate([{'$project': {'_id': 0,'new':'5'}}])
P: 同上
結果 => [{'new': '5'}, {'new': '5'}, {'new': '5'}]
注:'new'是在投影的時候新加的,會被投影。可是加了此新值,除了_id,其餘屬性默認都不會被投影了
$skip (管道跳過,原理同前面講過skip() 略)
-
$limit(管道截取,原理同前面講過的limit() )
M: queryset = table.aggregate([{'$skip': 1},{'$limit':1}])
P: 同上
解釋:
一共三條文檔, skip跳過了第一條,從第二條開始取,limit取一條,因此最終取的是第二條
-
$sort (管道排序,同上,不解釋)
M: queryset = table.aggregate([{'$sort':{'age':1}}])
P: 同上
-
$unwind(管道展開數組, 至關於 數學的 分配律)
數據條件 => {"name" : "Tom", "hobby" : [ "sing", "dance" ]}
path小參數:
M: table.aggregate([{'$unwind':{'path': '$hobby'}}]) # 注意 path是語法關鍵詞
P: 同上
結果 =>
{ "_id" : xx, "name" : "Tom", "hobby" : "sing" }
{ "_id" : xx, "name" : "Tom", "hobby" : "dance" }
形象例子:
a * [b+c] => a*b + a*c
includeArrayIndex小參數:
M: queryset = table.aggregate([{'$unwind': {
'path':'$hobby',
'includeArrayIndex':'index' # 展開的同時會新增index字段記錄原索引
}}])
P: 同上
結果 =>
{"name" : "Tom", "hobby" : "sing", "index" : NumberLong(0) }
{"name" : "Tom", "hobby" : "dance", "index" : NumberLong(1) }
注意:
$unwind 上面有兩種特殊狀況:
狀況一:
文檔中無 hobby字段 或 hobby字段爲 空數組[]
那麼該文檔不參與unwind展開操做, 天然就不會顯示結果。
若想讓這種文檔也參與 unwind展開操做,那麼須要追加小參數
'preserveNullAndEmptyArrays':true # 與 path同級書寫
最終結果,這種字段的文檔也會被展現出來,而且 index會被賦予一個 null值
狀況二:
文檔中有 hobby字段,可是該字段的值並非數組
那麼該文檔 會 參與 unwind展開操做,而且會顯示出來, 一樣 index 會被賦予一個 null值
-
$lookup(使用方式一)
使用方式(一):集合關聯 ===> 個人理解是,至關於關係型數據庫的 多表查詢機制
集合 <=> 表 , 多表查詢 <=> 多集合查詢
自身集合 與 外集合 根據咱們指定的 關聯字段 關聯後, 若有關聯,
則新字段的值爲 [外集合的關聯文檔, 。。。], 有幾條文檔關聯,這個數組就會有幾條
廢話很少說,先從新建立兩個集合:
db.user.insertOne({'name':'貓', 'country': ['China','USA']}) # 一條
db.country.insertMany([{'name':'China'}, {'name':'USA'}]) # 兩條
table = db.user # 看好,我賦值了一下,下面直接寫table就好了
M: queryset = table.aggregate([{
'$lookup': {
'from': 'country', # 須要鏈接的另一個集合的名稱(外集合)
'localField': 'country', # (主集合)鏈接的 依據 字段
'foreignField': 'name', # (外集合)鏈接的 依據 字段
'as': 'new_field' # 最終關聯後查詢出來的數據,生成新字段,as用來起名
}
}])
P: 同上
結果 =>
{
"_id" : ObjectId("5d2a6f4dee909cc7dc316bf1"),
"name" : "貓",
"country" : [
"China",
"USA"
], # 這行以前應該不用解釋,這就是 user集合自己的數據,沒變
"new_field" : [ # 這行是新加的字段,後面解釋
{
"_id" : ObjectId("5d2a6fcbee909cc7dc316bf2"),
"name" : "China"
},
{
"_id" : ObjectId("5d2a6fcbee909cc7dc316bf3"),
"name" : "USA"
}
]
}
解釋:
1. new_field是咱們新添加的字段
2. 由於user集合和country集合 咱們給出了2個依據關聯字段
而且這兩個關聯字段 'China' 和 'USA' 的值都相等
因此最終 user集合的new_field字段中 會添加 兩條 country集合的文檔 到 [] 中
3. 若是無關聯, 那麼 new_field字段中的值 爲 空[]
-
$lookup(使用方式二):
使用方式二:不作集合的關聯,而是直接把(外集合)通過條件篩選,做爲新字段放到(主集合)中。
M: queryset = table.aggregate([{
'$lookup': {
'from': 'country', # 外集合
'let': {'coun': '$country'}, # 使(主集合)的變量 能夠放在(外集合)使用
'pipeline': [{ # 外集合的專屬管道,裏面只能夠用外集合的屬性
'$match': { # 由於設置了 let,因此這裏面能夠用主集合變量
'$expr': { # $expr使得$match裏面可使用 聚合操做
'$and': [
{'$eq': ['$name', 'China']}, # 注意,這是聚合的 $eq用法
{'$eq': ['$$coun',['China', 'USA']]}
]
}
}
}],
'as': 'new_field'
}
}])
P: 同上
解釋:
把(外集合) pipeline裏面按各類條件 查到的文檔, 做爲(主集合)new_field 的值。
固然,若是不須要主集合中的屬性,能夠捨棄 let 字段
-
$group (分組--統計種類)
用法1(分組--統計字段種類)
M: queryset = table.aggregate([{'$group': {'_id': '$name'}}]) # _id是固定寫法
P: 同上
結果 => [{'_id': '老鼠'}, {'_id': '狗'}, {'_id': '貓'}]
用法2(分組--聚合)
數據條件:
{ "name" : "貓", "country" : [ "China", "USA" ], "age" : 18 }
{ "name" : "狗", "country" : "Japna" }
{ "name" : "老鼠", "country" : "Korea", "age" : 12 }
{ "name" : "貓", "country" : "Japna" }
M: queryset = table.aggregate([{
'$group': {
'_id': '$name', # 根據name字段分組
'type_count': {'$sum': 1}, # 統計每一個分類的 個數
'ageCount': {'$sum': '$age'}, # 統計age字段的 數字和
'ageAvg': {'$avg': '$age'}, # 統計age字段的 平均值
'ageMin': {'$min': '$age'}, # 統計age字段的 最小值
'ageMax': {'$max': '$age'}, # 統計age字段的 最大值
}
}])
p: 同上
結果:
{
"_id" : "老鼠",
"type_count" : 1,
"ageCount" : 12,
"ageAvg" : 12,
"ageMin" : 12,
"ageMax" : 12
}
{
"_id" : "狗",
"type_count" : 1,
"ageCount" : 0,
"ageAvg" : null,
"ageMin" : null,
"ageMax" : null
}
{
"_id" : "貓",
"type_count" : 2,
"ageCount" : 18,
"ageAvg" : 18,
"ageMin" : 18,
"ageMax" : 18
}
注意:
若想直接對整個集合的 作統計,而不是分組再統計
把 _id改成 null便可 { _id: 'null' }
# (或者隨便寫一個匹配不到的 字符串或數字都行,分不了組,就自動給你統計整個集合了)
-
$out (聚合操做後,將結果寫入新集合)
"""
個人理解是重定向 操做, 或者理解爲 視圖 操做
寫入的集合若是存在,那麼會所有覆蓋(但保留索引)
聚合過程遇到錯誤,那麼會自動執行 ’回滾’操做
"""
M:
table.aggregate([
{ '$group': {'_id': '$name'} },
{ '$out': 'newCollection' }
])
P: 同上
最後驗證: db.newCollection.find() ,你就會看到新集合 及其 裏面的內容
聚合管道 ==> 第二個參數
table.aggregate([以前說的都是這裏面的參數], 下面說這個參數)
allowDiskUse: true
每一個聚合管道佔用內存需 < 16M, 過大就會出問題
allowDiskUse設置爲true, 會將內存的 寫入到臨時文件中,減緩內存壓力。
官方文檔:write data to the _tmp subdirectory in the dbPath directory
Default: /data/db on Linux and macOS, \data\db on Windows
它說: 默認在 dbPath配置變量下的 子目錄_tmp下, dbPath默認爲 : /data/db
M:
queryset = table.aggregate([{
'$group': {'_id': '$name'}}],
{'allowDiskUse': true}
)
P:
queryset = table.aggregate([{
'$group': {'_id': '$name'}}],
allowDiskUse=True, # 注意,這裏語法稍有不同
)
建立索引:
-
單鍵索引
M: table.createIndex({'name':1})
P: table.create_index([('name',-1)]) # -1表明逆序索引,注意是元組
-
聯合索引
索引命中:最左匹配原則 eg 1,2,3 這三個建立聯合索引, 可命中索引爲:【1,12,123】
M: table.createIndex( {'name':1}, {}, {} ) # 多個{}
P: table.create_index([ ('name',-1), (), () ]) # 多個元組
-
多鍵索引
多鍵是針對於數組來說的,建立單鍵的字段 指定爲 數組字段, 默認就會設置爲多鍵索引
-
惟一索引 (unique)
'''注意: 若是集合中,不一樣文檔的字段有重複,建立惟一索引的時候會報錯'''
M: table.createIndex({'name':1}, {'unique':true})
P: table.create_index([('name', 1),('counrty',1)], unique=True)
-
稀疏索引 (sparse)
eg:
一個集合中:
給 name建立 惟一索引
插入文檔1: 有 name字段
插入文檔2: 無 name字段 (MongoDB會在索引庫中,把沒有的字段的 索引設爲 {字段:null} )
再插入文檔3, 無name字段 --> 一樣也會把索引庫中 name設爲 null
可是就在這個時候,剛要把索引庫中的 name字段設爲 null的時候。。。
惟一索引告訴你:」 我這裏已經有了一個,{ name:null },請你滾 」
而後就無情的給你報錯了(重複索引字段)
那咋整啊, 別急,稀疏索引就是給你辦這事的
設置稀疏索引。 MongoDB就不會把 沒有的字段 加入到索引庫了
因此,索引庫裏面就不會自動添加 {字段: null}
從新再次插入文檔3, 無name字段, 可成功插入,不存在null的重複問題了
M: table.createIndex({'name':1}, {'unique':true, 'sparse':true})
P: table.create_index([('name', 1),('counrty',1)], unique=True, sparse=True)
-
查詢索引
M:queryset = table.getIndexes()
P: queryset = table.list_indexes()
-
刪除索引
方式1:
M: table.dropIndex('索引名') # 索引名可經過 上面查詢索引的指令查
P: table.drop_index('索引名')
方式2:
M: table.dropIndexes() # 刪除所有,_id除外, 想指定刪除多個,可用列表列出
P: table.drop_indexes()
-
查看索引性能(是否有效)
table.上面說過的任一函數().explain() # 鏈式調用 explain,表示列出此操做的性能
eg:
M: queryset = table.explain().find({'name':'貓'})
P: 同上
結果中找到:
queryPlanner -> winningPlan -> inputStage -> stage # stage結果對應說明以下
COLLSCAN # 未優化,仍是搜的整個集合
IXSCAN # 索引發到做用
索引對投影的優化:
queryPlanner -> winningPlan -> stage # stage結果對應說明以下
FETCH # 索引 對投影 未優化
PROJECTION # 索引 對投影 起到優化做用
索引對排序的優化:
同上 stage 最好 不是 sort
按索引 正序(逆序) 取數據, 這樣就有效避免了機械排序的過程