MongoDB慢查詢與索引

MongoDB慢查詢

慢查詢分析

  1. 開啓內置的慢查詢分析器
db.setProfilingLevel(n,m),n的取值可選0,1,2
  • 0:表示不記錄
  • 1:表示記錄慢速操作,如果值爲1,m需要傳慢查詢的閾值,單位爲ms
  • 2:表示記錄所有的讀寫操作

示例:

db.setProfilingLevel(1,3)
  1. 查詢監控結果
db.system.profile.find().sort({millis:-1}).limit(3)

MongoDB索引

什麼是索引?

索引是一種單獨的、物理的對數據庫表中一列或多列的值進行排序的一種存儲結構,它是某個表中一列或若干列值的集合和相應的指向表中物理標識這些值的數據頁的邏輯指針清單。索引的作用相當於圖書的目錄,可以根據目錄中的頁碼快速找到所需的內容。索引目標是提高數據庫的查詢效率,沒有索引的話,查詢會進行全表掃描(scaneverydocumentinacollection),數據量大時嚴重降低了查詢效率。默認情況下Mongo在一個集合(collection)創建時,自動地對集合的_id創建了唯一索引。

索引結構

MongoDB的索引結構爲B樹

B樹非葉子節點也存了數據,查詢效率不固定,最好的情況是O(1),在單次查詢的情況下平均性能是優於B+樹的。而MongoDB是被作爲一個單一查詢比較多,遍歷數據比較少的一個定位。所以採用了B樹。

那爲什麼不用單次性能更好的Hash結構呢?

因爲雖然遍歷數據的情況較少,但是對於遍歷數據也需要有相對較好的性能支持。Hash這種性能表現較爲極端的數據結構往往只能在簡單、極端的場景下使用。

索引分類

  1. 單鍵索引

MongoDB支持所有數據類型中的單個字段索引,並且可以在文檔的任何字段定義。對於單個字段索引,索引鍵的排序順序無關緊要,因爲MongoDB可以在任一方向讀取索引。

db.集合名.createIndex({"字段名":排序方式})

示例:

db.user.createIndex({"name":1})

創建後可以通過查詢索引命令查看是否創建成功。

db.user.getIndexes()
  1. 過期索引TTL

TTL索引是MongoDB中一種特殊的索引,可以支持文檔在一定時間之後自動過期刪除,目前TTL索引只能在單字段上建立,並且字段類型必須是日期類型。

db.集合名.createIndex({"日期字段":排序方式}, {expireAfterSeconds: 秒數})

示例:

db.user.createIndex({"bithday":1}, {expireAfterSeconds: 10})

創建過期索引後,有bithday字段的文檔會在約10秒後自動刪除。

  1. 複合索引

通常我們需要在多個字段上進行搜索,如果是這種情況,可以考慮使用複合索引。複合索引支持基於多個字段的索引,這擴展了索引的概念並將它們擴展到索引中的更大域。

建立複合索引需要注意:字段順序和索引方向。它也是遵循最左前綴原則。

db.集合名.createIndex( { "字段名1" : 排序方式, "字段名2" : 排序方式 } )
  1. 多鍵索引

針對屬性包含數組數據的情況,MongoDB支持針對數組中每一個element創建索引,支持Strings、numbers、nested documents。

示例:

//type是集合類型的數據,創建的就是多鍵索引
db.book.insert({title:"java",type:["技術","IT"]})

db.book.createIndex({type:1})
  1. 哈希索引

針對屬性的哈希值進行索引查詢,當要使用Hashed Index時,MongoDB能夠自動計算hash值來進行查詢。

db.集合.createIndex({"字段": "hashed"})
  1. 地理空間索引

針對地理空間座標數據創建索引。2dsphere索引:用於存儲和查找球面上的點。

2d索引:用於存儲和查找平面上的點。

db.集合名.ensureIndex({字段名:"2dsphere"})

示例:

//插入數據
db.company.insert({
    loc:{type:"Point",coordinates:[116.482451,39.914176]},
    name:"大望路",
    category:"Parks"
})

//創建索引
db.company.ensureIndex({loc:"2dsphere"})

//查詢範圍內的數據
db.company.find({
    "loc":{
    "$geoWithin":{
        "$center":[[116.482450,39.914176],0.05]
    }
}
})

//距離指定位置最近的2個點
db.company.aggregate([
    {
        $geoNear: {
          near: {
              type: "Point",
              coordinates: [ 116.472451,39.814176]
          },
          key:"loc",
          distanceField: "dist.calculated",
          spherical: true
        }
    },
    {
        $limit: 2
    }
    ])

索引管理

  1. 創建索引並在後臺運行

有時數據量大的時候,創建索引的動作是比較耗費時間的,這時後臺運行就比較有用了。

db.COLLECTION_NAME.createIndex({"字段":排序方式}, {background: true});
  1. 查詢某個集合的索引
db.COLLECTION_NAME.getIndexes()
  1. 查看索引大小
db.COLLECTION_NAME.totalIndexSize()
  1. 索引重建
db.COLLECTION_NAME.reIndex()
  1. 索引刪除
db.COLLECTION_NAME.dropIndex("INDEX-NAME")
db.COLLECTION_NAME.dropIndexes()
注意: _id 對應的索引是刪除不了的

Explain分析

explain()是一個查詢分析的方法,它還可以接收不同的參數來查看更詳細的查詢計劃。

簡單示例:

db.user.find().explain()
db.user.find({name:"test1"}).explain("executionStats")

參數介紹:

  • queryPlanner:queryPlanner是默認參數,具體執行計劃信息參考下面的表格
  • executionStats:executionStats會返回執行計劃的一些統計信息(有些版本中和allPlansExecution等同)。
  • allPlansExecution:allPlansExecution用來獲取所有執行計劃,結果參數基本與上文相同
  1. queryPlanner參數查詢返回值含義
參數 含義
plannerVersion 查詢計劃版本
namespace 要查詢的集合(該值返回的是該query所查詢的表)數據庫.集合
indexFilterSet 針對該query是否有indexFilter
parsedQuery 查詢條件
winningPlan 被選中的執行計劃
winningPlan.stage 被選中執行計劃的stage(查詢方式),常見的有:COLLSCAN/全表掃描:(應該知道就是CollectionScan,就是所謂的“集合掃描”,和mysql中tablescan/heapscan類似,這個就是所謂的性能最爛最無奈的由來)、IXSCAN/索引掃描:(是IndexScan,這就說明我們已經命中索引了)、FETCH/根據索引去檢索文檔、SHARD_MERGE/合併分片結果、IDHACK/針對_id進行查詢等
winningPlan.inputStage 用來描述子stage,並且爲其父stage提供文檔和索引關鍵字。
winningPlan.stage的child stage 如果此處是IXSCAN,表示進行的是index scanning。
winningPlan.keyPattern 所掃描的index內容
winningPlan.indexName winning plan所選用的index。
winningPlan.isMultiKey 是否是Multikey,此處返回是false,如果索引建立在array上,此處將是true。
winningPlan.direction 此query的查詢順序,此處是forward,如果用了.sort({字段:-1})將顯示backward。
filter 過濾條件
winningPlan.indexBounds winningplan所掃描的索引範圍,如果沒有制定範圍就是[MaxKey,MinKey],這主要是直接定位到mongodb的chunck中去查找數據,加快數據讀取。
rejectedPlans 被拒絕的執行計劃的詳細返回,其中具體信息與winningPlan的返回中意義相同,故不在此贅述
serverInfo MongoDB服務器信息
  1. executionStats參數查詢返回值含義
參數 含義
executionSuccess 是否執行成功
nReturned 返回的文檔數
executionTimeMillis 執行耗時
totalKeysExamined 索引掃描次數
totalDocsExamined 文檔掃描次數
executionStages 這個分類下描述執行的狀態
stage 掃描方式,具體可選值與上文的相同
nReturned 查詢結果數量
executionTimeMillisEstimate 檢索document獲得數據的時間
inputStage.executionTimeMillisEstimate 該查詢掃描文檔 index所用時間
works 工作單元數,一個查詢會分解成小的工作單元
advanced 優先返回的結果數
docsExamined 文檔檢查數目,與totalDocsExamined一致。檢查了總共的document個數,而從返回上面的nReturned數量

這麼多返回值我們怎麼分析呢?

首先我們先造點數據:

for(var i=0;i<100000;i++){
    db.user.insert({
        name:"test"+i,
        explectSalary:10+i
    })
}

查詢耗時115

db.user.find({name:'test1'}).explain("allPlansExecution")

然後創建索引

db.user.createIndex({name:1})

再次查詢,查看耗時變爲了2。速度直線飆升。我們再對返回結果做一個分析:

{
        "queryPlanner" : {
                "plannerVersion" : 1,
                "namespace" : "test.user",
                "indexFilterSet" : false,
                "parsedQuery" : {
                        "name" : {
                                "$eq" : "test1"
                        }
                },
                "winningPlan" : {
                        "stage" : "FETCH",
                        "inputStage" : {
                                "stage" : "IXSCAN",
                                "keyPattern" : {
                                        "name" : 1
                                },
                                "indexName" : "name_1",
                                "isMultiKey" : false,
                                "multiKeyPaths" : {
                                        "name" : [ ]
                                },
                                "isUnique" : false,
                                "isSparse" : false,
                                "isPartial" : false,
                                "indexVersion" : 2,
                                "direction" : "forward",
                                "indexBounds" : {
                                        "name" : [
                                                "[\"test1\", \"test1\"]"
                                        ]
                                }
                        }
                },
                "rejectedPlans" : [ ]
        },
        "executionStats" : {
                "executionSuccess" : true,
                "nReturned" : 2,
                "executionTimeMillis" : 2,
                "totalKeysExamined" : 2,
                "totalDocsExamined" : 2,
                "executionStages" : {
                        "stage" : "FETCH",
                        "nReturned" : 2,
                        "executionTimeMillisEstimate" : 0,
                        "works" : 3,
                        "advanced" : 2,
                        "needTime" : 0,
                        "needYield" : 0,
                        "saveState" : 0,
                        "restoreState" : 0,
                        "isEOF" : 1,
                        "docsExamined" : 2,
                        "alreadyHasObj" : 0,
                        "inputStage" : {
                                "stage" : "IXSCAN",
                                "nReturned" : 2,
                                "executionTimeMillisEstimate" : 0,
                                "works" : 3,
                                "advanced" : 2,
                                "needTime" : 0,
                                "needYield" : 0,
                                "saveState" : 0,
                                "restoreState" : 0,
                                "isEOF" : 1,
                                "keyPattern" : {
                                        "name" : 1
                                },
                                "indexName" : "name_1",
                                "isMultiKey" : false,
                                "multiKeyPaths" : {
                                        "name" : [ ]
                                },
                                "isUnique" : false,
                                "isSparse" : false,
                                "isPartial" : false,
                                "indexVersion" : 2,
                                "direction" : "forward",
                                "indexBounds" : {
                                        "name" : [
                                                "[\"test1\", \"test1\"]"
                                        ]
                                },
                                "keysExamined" : 2,
                                "seeks" : 1,
                                "dupsTested" : 0,
                                "dupsDropped" : 0
                        }
                },
                "allPlansExecution" : [ ]
        },
        "serverInfo" : {
                "host" : "10.0.3.15",
                "port" : 27017,
                "version" : "4.2.21",
                "gitVersion" : "b0aeed9445ff41af07449fa757e1f231bce990b3"
        },
        "ok" : 1
}

重要參數介紹:

  • executionStats.executionTimeMillis 整體查詢時間
  • executionStats.executionStages.executionTimeMillisEstimate 該查詢檢索document獲得數據的時間
  • executionStats.inputStage.executionTimeMillisEstimate 該查詢掃描文檔index所用的時間
  • executionStats.nReturned 查詢返回的條數
  • executionStats.totalKeysExamined:索引掃描條數
  • executionStats.totalDocsExamined:文檔掃描條數

對於一個查詢,我們最理想的狀態是:nReturned=totalKeysExamined=totalDocsExamined

  • stage狀態:它的值有很多,如下所示:

類型列舉如下:

  • COLLSCAN:全表掃描
  • IXSCAN:索引掃描
  • FETCH:根據索引去檢索指定document
  • SHARD_MERGE:將各個分片返回數據進行merge
  • SORT:表明在內存中進行了排序
  • LIMIT:使用limit限制返回數
  • SKIP:使用skip進行跳過
  • IDHACK:針對_id進行查詢
  • SHARDING_FILTER:通過mongos對分片數據進行查詢
  • COUNT:利用db.coll.explain().count()之類進行count運算
  • TEXT:使用全文索引進行查詢時候的stage返回
  • PROJECTION:限定返回字段時候stage的返回

還有的是上面的組合

  • Fetch+IDHACK
  • Fetch+IXSCAN
  • Limit+(Fetch+IXSCAN)
  • PROJECTION+IXSCAN
  • SHARDING_FITER+IXSCAN
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章