MongoDB慢查詢與索引

2022-07-16 12:02:32

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