查詢優化器html
什麼時候查詢計劃緩存纔會變呢java
聯合索引的優化mongodb
聚合管道的優化數據庫
最指望看到的查詢組合json
最不指望看到的查詢組合數組
最左前綴原則緩存
效率極低的操做符併發
MongoDB 支持文檔集合中任何字段的索引,在默認狀況下,全部集合在 _id 字段上都有一個索引,應用程序和用戶能夠添加額外的索引來支持重要的查詢操做app
對於單字段索引和排序操做,索引鍵的排序順序(即升序或降序)可有可無,由於 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}。
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()
若要爲包含數組的字段創建索引,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中是禁止對兩個數組添加複合索引的,對兩個數組添加索引那麼索引大小將是爆炸增加,因此謹記在心。
能夠針對某個時間字段,指定文檔的過時時間(通過指定時間後過時 或 在某個時間點過時)
是指按照某個字段的hash值來創建索引,hash索引只能知足字段徹底匹配的查詢,不能知足範圍查詢等
能很好的解決一些場景,好比『查找附近的美食』、『查找附近的加油站』等
能解決快速文本查找的需求,好比,日誌平臺,相對日誌關鍵詞查找,若是經過正則來查找的話效率極低,這時就能夠經過文本索引的形式來進行查找
若要返回集合上全部索引的列表,則需使用驅動程序的 db.collection.getlndexes() 方法或相似方法。
例如,可以使用以下方法查看 records 集合上的全部索引:
db.records.getIndexes()
若要列出數據庫中全部集合的全部索引,則需在 MongoDB 的 Shell 客戶端中進行如下操做:
db.getCollectionNames().forEach(function(collection){ indexes = db[collection].getIndexes(); print("Indexes for " + collection + ":" ); printjson(indexes); });
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()
db.myCollection.reIndex() db.runCommand( { reIndex : 'myCollection' } )
一般這是沒必要要的,可是在集合的大小變更很大及集合在磁盤空間上佔用不少空間時重建索引纔有用。對於大數據量的集合來講,重建索引可能會很慢。
參數 | 類型 | 描述 |
---|---|---|
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. |
Mongo自帶了一個查詢優化器會爲咱們選擇最合適的查詢方案。
若是一個索引可以精確匹配一個查詢,那麼查詢優化器就會使用這個索引。
若是不能精確匹配呢?可能會有幾個索引都適合你的查詢,那MongoDB是怎樣選擇的呢?
當你查詢條件的順序和你索引的順序不一致的話,mongo會自動的調整查詢順序,保證你可使用上索引。
例如:你的查詢條件是(a,c,b)可是你的索引是(a,b,c)mongo會自動將你的查詢條件調整爲abc,尋找最優解。
然而管道中的索引使用狀況是極其不佳的,在管道中,只有在管道最開始時的match sort可使用到索引,一旦發生過project投射,group分組,lookup表關聯,unwind打散等操做後,就徹底沒法使用索引。
- Fetch+IDHACK
- Fetch+ixscan
- Limit+(Fetch+ixscan)
- PROJECTION+ixscan
- COLLSCAN(全表掃)
- SORT(使用sort可是無index)
- COUNTSCAN****(不使用索引進行count)
假定索引(a,b,c) 它可能知足的查詢以下:
1. a
2. a,b
3. a,b,c
4. a,c [該組合只能用a部分]
5. a, c, b [cb在查詢時會被優化換位置]
顯然,最左前綴的核心是查詢條件字段必須含有索引第一個字段
最左值儘量用最精確過濾性最好的值,不要用那種可能會用於範圍模糊查詢,用於排序的字段
- \(where和\)exists:這兩個操做符,徹底不能使用索引。
- \(ne和\)not:一般來講取反和不等於,可使用索引,可是效率極低,不是頗有效,每每也會退化成掃描全表。
- $nin:不包含,這個操做符也老是會全表掃描
- 對於管道中的索引,也很容易出現意外,只有在管道最開始時的match sort可使用到索引,一旦發生過project投射,group分組,lookup表關聯,unwind打散等操做後,就徹底沒法使用索引。
執行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") } } }
queryPlanner:查詢計劃的選擇器,首先進行查詢分析,最終選擇一個winningPlan,是explain返回的默認層面。
executionStats:爲執行統計層面,返回winningPlan的統計結果
allPlansExecution:爲返回全部執行計劃的統計,包括rejectedPlan
因此:咱們在查詢優化的時候,只須要關注queryPlanner, executionStats便可,由於queryPlanner爲咱們選擇出了winningPlan, 而executionStats爲咱們統計了winningPlan的全部關鍵數據。
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的返回中意義相同,故不在此贅述。
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從中派生結果集的最後階段。
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返回
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)
終止該請求。
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