目錄
-
索引的類型和屬性
- 單鍵索引
- 普通單鍵索引
- 索引子文檔字段
- 唯一索引
- 復合索引
- 多鍵值索引
- 過期索引
- 哈希索引
- 地理位置索引
- 文本索引
- 單鍵索引
-
索引操作方法
- 查看現有索引
- 列出數據庫的所有索引
- 刪除索引
- 重建索引
- 創建索引的參數
-
索引規則
-
查詢優化器
-
何時查詢計划緩存才會變呢
-
聯合索引的優化
-
聚合管道的優化
-
最期望看到的查詢組合
-
最不期望看到的查詢組合
-
最左前綴原則
-
效率極低的操作符
-
-
explain
- 介紹
- queryPlanner返回結果的意義
- executionStats返回結構的意義
- stage的類型的意義
-
運維命令
一.索引的類型和屬性
1.單鍵索引
①普通單鍵索引
MongoDB 支持文檔集合中任何字段的索引,在默認情況下,所有集合在 _id 字段上都有一個索引,應用程序和用戶可以添加額外的索引來支持重要的查詢操作
對於單字段索引和排序操作,索引鍵的排序順序(即升序或降序)無關緊要,因為 MongoDB 可以在任意方向上遍歷索引。
創建單鍵索引的語法結構如下:
# 1 為升序,-1 為降序
db.collection.createlndex ( { key: 1 } )
以下示例為插入一個文檔,並在 score 鍵上創建索引,具體步驟如下:
db.records.insert(
{
"score" : 1034,
"location" : { state: "NY", city: "New York"}
}
)
db.records.createTndex( { score: 1 } )
使用 score 字段進行查詢,再使用 explain() 函數,可以查看查詢過程:
db.records.find({score:1034}).explain()
②索引子文檔字段(或者"內嵌索引")
{
"address": {
"city": "Los Angeles",
"state": "California",
"pincode": "123"
},
"tags": [
"music",
"cricket",
"blogs"
],
"name": "Tom Benzamin"
}
假設我們需要通過city、state、pincode字段來檢索文檔,由於這些字段是子文檔的字段,所以我們需要對子文檔建立索引。
為子文檔的city字段創建索引,命令如下:
db.users.ensureIndex({"address.city":1})
對嵌套文檔本身“address”建立索引,與對嵌套文檔的某個字段(address.city)建立索引是完全不相同的。
對整個文檔建立索引,只有在使用文檔完整匹配時才會使用到這個索引,例如建立了這樣一個索引db.personInfos.createIndex({“address”:1}),那么只有使用db.personInfos.find({“address”:{“pincode”:”xxx”,”city”:”xxx”,""state":"xxx"}})這種完整匹配時才會使用到這個索引,使用db.personInfos.find({“address.city”:”xxx”})是不會使用到該索引的。
③唯一索引
唯一索引是索引具有的一種屬性,讓索引具備唯一性,確保這張表中,該條索引數據不會重復出現。在每一次insert和update操作時,都會進行索引的唯一性校驗,保證該索引的字段組合在表中唯一。
db.containers.createIndex({name: 1},{unique:true, background: true})
db.packages.createIndex({ appId: 1, version: 1 },{unique:true, background: true})
Mongo提供兩種建索引的方式foreground和background。
前台操作,它會阻塞用戶對數據的讀寫操作直到index構建完畢;
后台模式,不阻塞數據讀寫操作,獨立的后台線程異步構建索引,此時仍然允許對數據的讀寫操作。
創建索引時一定要寫{background: true}
創建索引時一定要寫{background: true}
創建索引時一定要寫{background: true}MongoDB中是只有庫級鎖的,創建索引時要添加參數{background: true}。
2.復合索引
MongoDB 支持復合索引,其中復合索引結構包含多個字段
復合索引可以支持在多個字段上進行的匹配查詢,語法結構如下:
db.collection.createIndex ({ <key1> : <type>, <key2> : <type2>, ...})
需要注意的是,在建立復合索引的時候一定要注意順序的問題,順序不同將導致查詢的結果也不相同。
如下語句創建復合索引:
db.records.createIndex ({ "score": 1, "location.state": 1 })
查看復合索引的查詢計划的語法如下:
db.records.find({score:1034, "location.state" : "NY"}).explain()
3.多鍵值索引(或者"數組索引")
若要為包含數組的字段建立索引,MongoDB 會為數組中的每個元素創建索引鍵。這些多鍵值索引支持對數組字段的高效查詢
建多鍵值索引的語法如下:
db.collecttion.createlndex( { <key>: < 1 or -1 > })
需要注意的是,如果集合中包含多個待索引字段是數組,則無法創建復合多鍵索引。
以下示例代碼展示插入文檔,並創建多鍵值索引:
db.survey.insert ({item : "ABC", ratings: [ 2, 5, 9 ]})
db.survey.createIndex({ratings:1})
db.survey.find({ratings:2}).explain()
對數組建立索引的代價是非常高的,他實際上是會對數組中的每一項都單獨建立索引,就相當於假設數組中有十項,那么就會在原基礎上,多出十倍的索引大小。如果有一百個一千個呢?
所以在mongo中是禁止對兩個數組添加復合索引的,對兩個數組添加索引那么索引大小將是爆炸增長,所以謹記在心。
4.過期索引(TTL)
可以針對某個時間字段,指定文檔的過期時間(經過指定時間后過期 或 在某個時間點過期)
5.哈希索引(Hashed Index)
是指按照某個字段的hash值來建立索引,hash索引只能滿足字段完全匹配的查詢,不能滿足范圍查詢等
6.地理位置索引(Geospatial Index)
能很好的解決一些場景,比如『查找附近的美食』、『查找附近的加油站』等
7.文本索引(Text Index)
能解決快速文本查找的需求,比如,日志平台,相對日志關鍵詞查找,如果通過正則來查找的話效率極低,這時就可以通過文本索引的形式來進行查找
二.索引操作方法
1.查看現有索引
若要返回集合上所有索引的列表,則需使用驅動程序的 db.collection.getlndexes() 方法或類似方法。
例如,可使用如下方法查看 records 集合上的所有索引:
db.records.getIndexes()
2.列出數據庫的所有索引
若要列出數據庫中所有集合的所有索引,則需在 MongoDB 的 Shell 客戶端中進行以下操作:
db.getCollectionNames().forEach(function(collection){
indexes = db[collection].getIndexes();
print("Indexes for " + collection + ":" );
printjson(indexes);
});
3.刪除索引
MongoDB 提供的兩種從集合中刪除索引的方法如下:
# 刪除單個索引
db.collection.dropIndex("")
# 刪除集合的全部索引
db.collection.dropIndexes()
若要刪除特定索引,則可使用該 db.collection.droplndex() 方法。
例如,以下操作將刪除集合中 score 字段的升序索引:
db.records.dropIndex ({ "score" : 1 }) //升序降序不能錯,如果為-1,則提示無索引
還可以使用 db.collection.droplndexes() 刪除除 _id 索引之外的所有索引。
例如,以下命令將從 records 集合中刪除所有索引:
db.records.dropIndexes()
4.重建索引
db.myCollection.reIndex()
db.runCommand( { reIndex : 'myCollection' } )
通常這是不必要的,但是在集合的大小變動很大及集合在磁盤空間上占用很多空間時重建索引才有用。對於大數據量的集合來說,重建索引可能會很慢。
MongoDB中索引是大小寫敏感的。
5.創建索引的參數
參數 | 類型 | 描述 |
---|---|---|
background | Boolean | 建索引過程會阻塞其它數據庫操作,background可指定以后台方式創建索引,即增加 "background" 可選參數。 "background" 默認值為false。 |
unique | Boolean | 建立的索引是否唯一。指定為true創建唯一索引。默認值為false. |
name | string | 索引的名稱。如果未指定,MongoDB的通過連接索引的字段名和排序順序生成一個索引名稱。 |
dropDups | Boolean | 3.0+版本已廢棄。在建立唯一索引時是否刪除重復記錄,指定 true 創建唯一索引。默認值為 false. |
sparse | Boolean | 對文檔中不存在的字段數據不啟用索引;這個參數需要特別注意,如果設置為true的話,在索引字段中不會查詢出不包含對應字段的文檔.。默認值為 false. |
expireAfterSeconds | integer | 指定一個以秒為單位的數值,完成 TTL設定,設定集合的生存時間。 |
v | index version | 索引的版本號。默認的索引版本取決於mongod創建索引時運行的版本。 |
weights | document | 索引權重值,數值在 1 到 99,999 之間,表示該索引相對於其他索引字段的得分權重。 |
default_language | string | 對於文本索引,該參數決定了停用詞及詞干和詞器的規則的列表。 默認為英語 |
language_override | string | 對於文本索引,該參數指定了包含在文檔中的字段名,語言覆蓋默認的language,默認值為 language. |
三.索引規則
1.查詢優化器
Mongo自帶了一個查詢優化器會為我們選擇最合適的查詢方案。
如果一個索引能夠精確匹配一個查詢,那么查詢優化器就會使用這個索引。
如果不能精確匹配呢?可能會有幾個索引都適合你的查詢,那MongoDB是怎樣選擇的呢?
- MongoDB的查詢計划會將多個索引並行的去執行,最先返回第101個結果的就是勝者,其他查詢計划都會被終止,執行優勝的查詢計划;
- 這個查詢計划會被緩存,接下來相同的查詢條件都會使用它;
2.何時查詢計划緩存才會變呢?
- 在計划評估之后表發生了比較大的數據波動,查詢優化器就會重新挑選可行的查詢計划
- 建立索引時
- 每執行1000次查詢之后,查詢優化器就會重新評估查詢計划
3.聯合索引的優化
當你查詢條件的順序和你索引的順序不一致的話,mongo會自動的調整查詢順序,保證你可以使用上索引。
例如:你的查詢條件是(a,c,b)但是你的索引是(a,b,c)mongo會自動將你的查詢條件調整為abc,尋找最優解。
4.聚合管道的優化
- 如果管道中不需要使用一個完整的文檔的全部字段的話,管道不會將多余字段進行傳遞
- $sort 和 $limit 合並,在內存中只會維護limit個數量的文檔,不需要將所有的文檔維護在內存中,大大降低內存中sort的壓力
然而管道中的索引使用情況是極其不佳的,在管道中,只有在管道最開始時的match sort可以使用到索引,一旦發生過project投射,group分組,lookup表關聯,unwind打散等操作后,就完全無法使用索引。
5.最期望看到的查詢組合
- Fetch+IDHACK
- Fetch+ixscan
- Limit+(Fetch+ixscan)
- PROJECTION+ixscan
6. 最不期望看到的查詢組合
- COLLSCAN(全表掃)
- SORT(使用sort但是無index)
- COUNTSCAN****(不使用索引進行count)
7. 最左前綴原則
假定索引(a,b,c) 它可能滿足的查詢如下:
1. a
2. a,b
3. a,b,c
4. a,c [該組合只能用a部分]
5. a, c, b [cb在查詢時會被優化換位置]
顯然,最左前綴的核心是查詢條件字段必須含有索引第一個字段
最左值盡可能用最精確過濾性最好的值,不要用那種可能會用於范圍模糊查詢,用於排序的字段
8. 效率極低的操作符
- \(where和\)exists:這兩個操作符,完全不能使用索引。
- \(ne和\)not:通常來說取反和不等於,可以使用索引,但是效率極低,不是很有效,往往也會退化成掃描全表。
- $nin:不包含,這個操作符也總是會全表掃描
- 對於管道中的索引,也很容易出現意外,只有在管道最開始時的match sort可以使用到索引,一旦發生過project投射,group分組,lookup表關聯,unwind打散等操作后,就完全無法使用索引。
四.explain
執行explain
db.union_recipe.find({"name" : /.*雞.*/i,"foodTags.text":"魯菜"}).explain("executionStats")
查詢出來的計划
{
"queryPlanner": {
"plannerVersion": NumberInt("1"),
"namespace": "iof_prod_recipe.union_recipe",
"indexFilterSet": false,
"parsedQuery": {
"$and": [
{
"foodTags.text": {
"$eq": "魯菜"
}
},
{
"name": {
"$regex": ".*雞.*",
"$options": "i"
}
}
]
},
"winningPlan": {
# 根據內層階段樹查到的索引去抓取完整的文檔
"stage": "FETCH",
"filter": {
"name": {
"$regex": ".*雞.*",
"$options": "i"
}
},
# 每個階段將自己的查詢結果傳遞給父階段樹,所以從里往外讀Explain
"inputStage": {
# IXSCAN該階段使用了索引進行掃描
"stage": "IXSCAN",
# 使用了 foodTags.text: -1 這條索引
"keyPattern": {
"foodTags.text": -1
},
"indexName": "foodTags.text_-1",
"isMultiKey": true,
"multiKeyPaths": {
"foodTags.text": [
"foodTags"
]
},
"isUnique": false,
"isSparse": false,
"isPartial": false,
"indexVersion": NumberInt("2"),
"direction": "forward",
"indexBounds": {
"foodTags.text": [
"[\"魯菜\", \"魯菜\"]"
]
}
}
},
"rejectedPlans": [
{
"stage": "FETCH",
"filter": {
"foodTags.text": {
"$eq": "魯菜"
}
},
"inputStage": {
"stage": "IXSCAN",
"filter": {
"name": {
"$regex": ".*雞.*",
"$options": "i"
}
},
"keyPattern": {
"name": 1
},
"indexName": "name_1",
"isMultiKey": false,
"multiKeyPaths": {
"name": [ ]
},
"isUnique": false,
"isSparse": false,
"isPartial": false,
"indexVersion": NumberInt("2"),
"direction": "forward",
"indexBounds": {
"name": [
"[\"\", {})",
"[/.*雞.*/i, /.*雞.*/i]"
]
}
}
}
]
},
"executionStats": {
"executionSuccess": true,
"nReturned": NumberInt("49"),
"executionTimeMillis": NumberInt("2"),
"totalKeysExamined": NumberInt("300"),
"totalDocsExamined": NumberInt("300"),
"executionStages": {
"stage": "FETCH",
"filter": {
"name": {
"$regex": ".*雞.*",
"$options": "i"
}
},
"nReturned": NumberInt("49"),
"executionTimeMillisEstimate": NumberInt("0"),
"works": NumberInt("302"),
"advanced": NumberInt("49"),
"needTime": NumberInt("251"),
"needYield": NumberInt("0"),
"saveState": NumberInt("5"),
"restoreState": NumberInt("5"),
"isEOF": NumberInt("1"),
"invalidates": NumberInt("0"),
"docsExamined": NumberInt("300"),
"alreadyHasObj": NumberInt("0"),
"inputStage": {
"stage": "IXSCAN",
"nReturned": NumberInt("300"),
"executionTimeMillisEstimate": NumberInt("0"),
"works": NumberInt("301"),
"advanced": NumberInt("300"),
"needTime": NumberInt("0"),
"needYield": NumberInt("0"),
"saveState": NumberInt("5"),
"restoreState": NumberInt("5"),
"isEOF": NumberInt("1"),
"invalidates": NumberInt("0"),
"keyPattern": {
"foodTags.text": -1
},
"indexName": "foodTags.text_-1",
"isMultiKey": true,
"multiKeyPaths": {
"foodTags.text": [
"foodTags"
]
},
"isUnique": false,
"isSparse": false,
"isPartial": false,
"indexVersion": NumberInt("2"),
"direction": "forward",
"indexBounds": {
"foodTags.text": [
"[\"魯菜\", \"魯菜\"]"
]
},
"keysExamined": NumberInt("300"),
"seeks": NumberInt("1"),
"dupsTested": NumberInt("300"),
"dupsDropped": NumberInt("0"),
"seenInvalidated": NumberInt("0")
}
}
},
"ok": 1,
"operationTime": Timestamp(1598602456, 1),
"$clusterTime": {
"clusterTime": Timestamp(1598602456, 1),
"signature": {
"hash": BinData(0, "/t+ZhDHuT6EtZMFyqmesvq9Rlfk="),
"keyId": NumberLong("6838110804550615041")
}
}
}
1.介紹
queryPlanner:查詢計划的選擇器,首先進行查詢分析,最終選擇一個winningPlan,是explain返回的默認層面。
executionStats:為執行統計層面,返回winningPlan的統計結果
allPlansExecution:為返回所有執行計划的統計,包括rejectedPlan
所以:我們在查詢優化的時候,只需要關注queryPlanner, executionStats即可,因為queryPlanner為我們選擇出了winningPlan, 而executionStats為我們統計了winningPlan的所有關鍵數據。
2.queryPlanner返回結果的意義
explain.queryPlanner: queryPlanner的返回
explain.queryPlanner.namespace:該值返回的是該query所查詢的表
explain.queryPlanner.indexFilterSet:針對該query是否有indexfilter
explain.queryPlanner.winningPlan:查詢優化器針對該query所返回的最優執行計划的詳細內容。
explain.queryPlanner.winningPlan.stage:最優執行計划的stage,這里返回是FETCH,可以理解為通過返回的index位置去檢索具體的文檔(stage有數個模式,將在后文中進行詳解)。
Explain.queryPlanner.winningPlan.inputStage:用來描述子stage,並且為其父stage提供文檔和索引關鍵字。
explain.queryPlanner.winningPlan.stage的child stage,此處是IXSCAN,表示進行的是index scanning。
explain.queryPlanner.winningPlan.keyPattern:所掃描的index內容,此處是did:1,status:1,modify_time: -1與scid : 1
explain.queryPlanner.winningPlan.indexName:winning plan所選用的index。
explain.queryPlanner.winningPlan.isMultiKey是否是Multikey,此處返回是false,如果索引建立在array上,此處將是true。
explain.queryPlanner.winningPlan.direction:此query的查詢順序,此處是forward,如果用了.sort({modify_time:-1})將顯示backward。
explain.queryPlanner.winningPlan.indexBounds:winningplan所掃描的索引范圍,如果沒有制定范圍就是[MaxKey, MinKey],這主要是直接定位到mongodb的chunck中去查找數據,加快數據讀取。
explain.queryPlanner.rejectedPlans:其他執行計划(非最優而被查詢優化器reject的)的詳細返回,其中具體信息與winningPlan的返回中意義相同,故不在此贅述。
3.executionStats返回結構的意義
executionStats.executionSuccess:是否執行成功
executionStats.nReturned:滿足查詢條件的文檔個數,即查詢的返回條數
executionStats.executionTimeMillis:整體執行時間
executionStats.totalKeysExamined:索引整體掃描的文檔個數,和早起版本的nscanned 是一樣的
executionStats.totalDocsExamined:document掃描個數, 和早期版本中的nscannedObjects 是一樣的
executionStats.executionStages:整個winningPlan執行樹的詳細信息,一個executionStages包含一個或者多個inputStages
executionStats.executionStages.stage:這里是FETCH去掃描對於documents,后面會專門用來解釋大部分查詢使用到的各種stage的意思
executionStats.executionStages.nReturned:由於是FETCH,所以這里該值與executionStats.nReturned一致
executionStats.executionStages.docsExamined:與executionStats.totalDocsExamined一致executionStats.inputStage中的與上述理解方式相同
explain.executionStats.executionStages.works:被查詢執行階段所操作的“工作單元(work units)”數。
explain.executionStats.executionStages.advanced:優先返回給父stage的中間結果集中文檔個數
explain.executionStats.executionStages.isEOF:查詢執行是否已經到了數據流的末尾
這些值的初始值都是0。Works的 值當isEOF為1時要比nReturned大1, isEOF為0是相同。
explain 結果將查詢計划以階段樹的形式呈現。
每個階段將其結果(文檔或索引鍵)傳遞給父節點。
中間節點操縱由子節點產生的文檔或索引鍵。
根節點是MongoDB從中派生結果集的最后階段。
4.stage的類型的意義
COLLSCAN :全表掃描
IXSCAN:索引掃描
FETCH::根據索引去檢索指定document
SHARD_MERGE:各個分片返回數據進行merge
SORT:表明在內存中進行了排序(與前期版本的scanAndOrder:true一致)
SORT_MERGE:表明在內存中進行了排序后再合並
LIMIT:使用limit限制返回數
SKIP:使用skip進行跳過
IDHACK:針對_id進行查詢
SHARDING_FILTER:通過mongos對分片數據進行查詢
COUNT:利用db.coll.count()之類進行count運算
COUNTSCAN:count不使用用Index進行count時的stage返回
COUNT_SCAN:count使用了Index進行count時的stage返回
SUBPLA:未使用到索引的$or查詢的stage返回
TEXT:使用全文索引進行查詢時候的stage返回
五.常用操作
1.分析MongoDB數據庫正在執行的請求
db.currentOp()
{
"desc" : "conn632530",
"threadId" : "140298196924160",
"connectionId" : 632530,
"client" : "11.192.159.236:57052",
"active" : true,
"opid" : 1008837885,
"secs_running" : 0,
"microsecs_running" : NumberLong(70),
"op" : "update",
"ns" : "mygame.players",
"query" : {
"uid" : NumberLong(31577677)
},
"numYields" : 0,
"locks" : {
"Global" : "w",
"Database" : "w",
"Collection" : "w"
},
....
},
字段 | 返回值說明 |
---|---|
client | 該請求是由哪個客戶端發起的。 |
opid | 操作的唯一標識符。說明 如果有需要,可以通過db.killOp(opid)直接終止該操作。 |
secs_running | 表示該操作已經執行的時間,單位為秒。如果該字段返回的值特別大,需要查看請求是否合理。 |
microsecs_running | 表示該操作已經執行的時間,單位為微秒。如果該字段返回的值特別大,需要查看請求是否合理。 |
ns | 該操作目標集合。 |
op | 表示操作的類型。通常是查詢、插入、更新、刪除中的一種。 |
locks | 跟鎖相關的信息,詳情請參見並發介紹,本文不做詳細介紹。 |
如果發現有異常的請求,您可以找到該請求對應的opid,執行db.killOp(opid)
終止該請求。
2.查看該數據下的慢請求日志
db.system.profile.find().pretty();
分析慢請求日志,查找引起MongoDB CPU使用率升高的原因。查看到該請求進行了全表掃描
{
"op" : "query",
"ns" : "123.testCollection",
"command" : {
"find" : "testCollection",
"filter" : {
"name" : "zhangsan"
},
"$db" : "123"
},
"keysExamined" : 0,
"docsExamined" : 11000000,
"cursorExhausted" : true,
"numYield" : 85977,
"nreturned" : 0,
"locks" : {
"Global" : {
"acquireCount" : {
"r" : NumberLong(85978)
}
},
"Database" : {
"acquireCount" : {
"r" : NumberLong(85978)
}
},
"Collection" : {
"acquireCount" : {
"r" : NumberLong(85978)
}
}
},
"responseLength" : 232,
"protocol" : "op_command",
"millis" : 19428,
"planSummary" : "COLLSCAN",
"execStats" : {
"stage" : "COLLSCAN",
"filter" : {
"name" : {
"$eq" : "zhangsan"
}
},
"nReturned" : 0,
"executionTimeMillisEstimate" : 18233,
"works" : 11000002,
"advanced" : 0,
"needTime" : 11000001,
"needYield" : 0,
"saveState" : 85977,
"restoreState" : 85977,
"isEOF" : 1,
"invalidates" : 0,
"direction" : "forward",
....in"
}
],
"user" : "root@admin"
}
通常在慢請求日志中,您需要重點關注以下幾點。
-
全表掃描(關鍵字: COLLSCAN、 docsExamined )
全集合(表)掃描COLLSCAN 。
當一個操作請求(如查詢、更新、刪除等)需要全表掃描時,將非常占用CPU資源。在查看慢請求日志時發現COLLSCAN關鍵字,很可能是這些查詢占用了CPU資源。說明:
如果這種請求比較頻繁,建議對查詢的字段建立索引的方式來優化。
通過查看docsExamined的值,可以查看到一個查詢掃描了多少文檔。該值越大,請求所占用的CPU開銷越大。 -
不合理的索引(關鍵字: IXSCAN、keysExamined )
說明:
索引不是越多越好,索引過多會影響寫入、更新的性能。
如果您的應用偏向於寫操作,索引可能會影響性能。
通過查看keysExamined字段,可以查看到 一個使用了索引的查詢,掃描了多少條索引。該值越大,CPU開銷越大。
如果索引建立的不太合理,或者是匹配的結果很多。這樣即使使用索引,請求開銷也不會優化很多,執行的速度也會很慢。
-
大量數據排序(關鍵字: SORT、hasSortStage )
當查詢請求里包含排序的時候, system.profile 集合里的hasSortStage字段會為 true 。
如果排序無法通 過索引滿足,MongoDB會在查詢結果中進行排序。
而排序這個動作將非常消耗CPU資源,這種情況需要對經常排序的字段建立索引的方式進行優化。
說明 當您在system.profile集合里發現SORT關鍵字時,可以考慮通過索引來優化排序。
-
其他還有諸如建立索引、aggregation(遍歷、查詢、更新、排序等動作的組合) 等操作也可能非常耗CPU資源,但本質上也是上述幾種場景。
站在巨人肩膀上摘蘋果
http://c.biancheng.net/view/6558.html
https://docs.mongodb.org/v3.0/reference/explain-results/
https://zhuanlan.zhihu.com/p/77971681
https://www.jianshu.com/p/2b09821a365d
https://www.imooc.com/article/285899
https://blog.csdn.net/weixin_33446857/article/details/83085018
https://www.runoob.com/mongodb/mongodb-advanced-indexing.html