天天看點

MongoDB優化淺析

一、MongoDB優化整體思路

  MongoDB的查詢語句優化與關系型資料庫類似,簡單來說就是通過慢查詢日志找出慢查詢語句,然後通過執行計劃進行分析,最後根據實際情況進行優化。

二、慢查詢日志分析

2.1 簡介

  在MongoDB中,慢查詢日志被叫做Profiler,我們可以通過設定Profiler來記錄慢查詢語句;然後就可以根據慢查詢日志中的内容進行優化分析了。

  MongoDB的慢查詢日志記錄在system.profile這個集合中,預設情況下慢查詢日志是關閉的,可以在資料庫級别上或者是節點級别上進行配置。

2.2 慢查詢日志的使用

  MongoDB有兩種方式可以對慢查詢日志進行配置:

1、直接在啟動參數或者配置檔案裡進行設定:

#傳統配置檔案格式
profile = 1
slowms = 100 #預設為100ms
#YAML配置檔案格式
operationProfiling:
   mode: <string>  # 預設為 off,可選值 off、slowOp、all
   slowOpThresholdMs: <int> # 門檻值,預設值為100ms
   slowOpSampleRate: <double> #随機采集慢查詢的百分比值,sampleRate值預設為1,表示都采集,0.5 表示采集50%的内容           

2、通過db.setProfilingLevel(level,slowms) 語句設定慢查詢級别和門檻值;

級别 含義
關閉慢查詢
1 隻記錄超過門檻值的查詢
2 記錄所有查詢

  MongoDB可以通過db.getProfilingStatus()擷取慢查詢日志的相關配置資訊

#  指定資料庫,并指定門檻值慢查詢 ,超過10毫秒的查詢被記錄
use test
db.setProfilingLevel(1, { slowms: 10 })
#  随機采集慢查詢的百分比值,sampleRate 值預設為1,表示都采集,0.5 表示采集50%的内容。
db.setProfilingLevel(1, { sampleRate: 0.5 }) 
#查詢大于等于5ms的時間
db.system.profile.find( { millis : { $gt : 5 } } )
# 查詢最近的5個慢查詢日志
db.system.profile.find().limit(5).sort( { ts : -1 } )
# 查詢除指令類型為'command'的日志
db.system.profile.find( { op: { $ne : 'command' } } )
# 查詢資料庫為test集合為t1的日志
db.system.profile.find( { ns : 'test.t1' } )
# 查詢時間從 2019-09-01 0點到 2012-10-01 0點之間的日志
db.system.profile.find({
  ts : {
    $gt: new ISODate("2019-09-01T00:00:00Z"),
    $lt: new ISODate("2019-10-01T00:00:00Z")
  }
})           

2.3 慢查詢日志内容詳解

{ 
"op" : "query", 
"ns" : "test.data", 
"command" : { "find" : "data", "filter" : { "id" : { "$gt" : 2, "$lt" : 10 }, "name" : "ZJ" }, "lsid" : { "id" : UUID("2fc9dd75-6731-4426-b0ba-38e1a5d7317e") }, "$db" : "test" }, 
"keysExamined" : 0,
 "docsExamined" : 1000000,
 "cursorExhausted" : true,
 "numYield" : 7812, 
 "nreturned" : 7, 
 "queryHash" : "17295960",
 "planCacheKey" : "17295960",
 "locks" : { "ParallelBatchWriterMode" : { "acquireCount" : { "r" : NumberLong(1) } }, "ReplicationStateTransition" : { "acquireCount" : { "w" : NumberLong(7814) } }, "Global" : { "acquireCount" : { "r" : NumberLong(7814) } }, "Database" : { "acquireCount" : { "r" : NumberLong(7813) } }, "Collection" : { "acquireCount" : { "r" : NumberLong(7813) } }, "Mutex" : { "acquireCount" : { "r" : NumberLong(1) } } },
 "flowControl" : {  }, 
 "storage" : {  }, 
 "responseLength" : 546, 
 "protocol" : "op_msg",
 "millis" : 623, 
 "planSummary" : "COLLSCAN",
 "execStats" : { "stage" : "COLLSCAN", "filter" : { "$and" : [ { "name" : { "$eq" : "ZJ" } }, { "id" : { "$lt" : 10 } }, { "id" : { "$gt" : 2 } } ] }, "nReturned" : 7, "executionTimeMillisEstimate" : 2, "works" : 1000002, "advanced" : 7, "needTime" : 999994, "needYield" : 0, "saveState" : 7812, "restoreState" : 7812, "isEOF" : 1, "direction" : "forward", "docsExamined" : 1000000 }, 
 "ts" : ISODate("2019-10-24T04:00:54.221Z"), 
 "client" : "127.0.0.1",
 "appName" : "MongoDB Shell", 
 "allUsers" : [ ], "user" : "" 
 }           

  上面是我截取慢查詢日志中的一個查詢語句,其中可以把它拆解為幾個部分來了解:

  • "op"

  該項表明該慢日志的種類,主要包含如下幾類:

insert
query
update
remove
getmore
command           
  • "ns"

  該項表明該慢日志是哪個庫下的哪個集合所對應的慢日志

  • "command"

  該項詳細輸出了慢日志的具體語句和行為

  • "keysExamined"

  該項表明為了找出最終結果MongoDB搜尋了多少個key

  • "docsExamined"

  該項表明為了找出最終結果MongoDB搜尋了多少個文檔

  • "numYield"

  為了讓别的操作完成而屈服的次數,一般發生在需要通路的資料尚未被完全讀取到記憶體中,MongoDB會優先完成在記憶體中的操作

  • "nreturned"

  該項表明傳回的記錄數

  • "locks"

  該項表明在操作中産生的鎖的相關資訊

  • 鎖的種類:
Global 全局鎖
MMAPV1Journal MMAPV1同步日志時加的一種鎖
Database 資料庫鎖
Collection 集合鎖
Metadata 中繼資料鎖
oplog oplog鎖
  • 鎖的模式:
R 共享S鎖
W 排他X鎖
r 意向共享IS鎖
w 意向排他IX
  • "responseLength"

  結果傳回的大小,機關為bytes,該值如果過大,則需考慮limit()等方式減少輸出結果

  • "millis"

  該操作從開始到結束耗時多少,機關為ms

  • "execStats"

  包含了該操作執行的詳細資訊

  • "ts"

  該操作執行時的時間

  • "client"

  哪個用戶端發起的該操作,并顯示出該用戶端的ip或hostname

  • "allUsers"

  哪個認證使用者執行的該操作

  • "user"

  是否認證使用者執行該操作,如認證後使用其他使用者操作,該項為空

三、執行計劃分析

3.1 簡介

  MongoDB檢視執行計劃的文法:

db.collection.find().explain()            

  MongoDB執行計劃三種模式:

  • queryPlanner Mode:隻會顯示 winning plan 的 queryPlanner,自建MongoDB預設模式
  • executionStats Mode:隻會顯示 winning plan 的 queryPlanner + executionStats
  • allPlansExecution Mode:會顯示所有執行計劃的 queryPlanner + executionStats,阿裡雲MongoDB預設模式

3.2 執行計劃内容詳解

{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "test.data",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "$and" : [
                {
                    "name" : {
                        "$eq" : "ZJ"
                    }
                },
                {
                    "id" : {
                        "$lt" : 10
                    }
                },
                {
                    "id" : {
                        "$gt" : 2
                    }
                }
            ]
        },
        "queryHash" : "17295960",
        "planCacheKey" : "17295960",
        "winningPlan" : {
            "stage" : "COLLSCAN",
            "filter" : {
                "$and" : [
                    {
                        "name" : {
                            "$eq" : "ZJ"
                        }
                    },
                    {
                        "id" : {
                            "$lt" : 10
                        }
                    },
                    {
                        "id" : {
                            "$gt" : 2
                        }
                    }
                ]
            },
            "direction" : "forward"
        },
        "rejectedPlans" : [ ]
    },
    "executionStats" : {
        "executionSuccess" : true,
        "nReturned" : 7,
        "executionTimeMillis" : 650,
        "totalKeysExamined" : 0,
        "totalDocsExamined" : 1000000,
        "executionStages" : {
            "stage" : "COLLSCAN",
            "filter" : {
                "$and" : [
                    {
                        "name" : {
                            "$eq" : "ZJ"
                        }
                    },
                    {
                        "id" : {
                            "$lt" : 10
                        }
                    },
                    {
                        "id" : {
                            "$gt" : 2
                        }
                    }
                ]
            },
            "nReturned" : 7,
            "executionTimeMillisEstimate" : 4,
            "works" : 1000002,
            "advanced" : 7,
            "needTime" : 999994,
            "needYield" : 0,
            "saveState" : 7812,
            "restoreState" : 7812,
            "isEOF" : 1,
            "direction" : "forward",
            "docsExamined" : 1000000
        },
        "allPlansExecution" : [ ]
    },
    "serverInfo" : {
        "host" : "xsj",
        "port" : 27017,
        "version" : "4.2.0",
        "gitVersion" : "a4b751dcf51dd249c5865812b390cfd1c0129c30"
    },
    "ok" : 1
}           

  上面這條語句的執行計劃可以拆解為幾個部分來了解:

  • queryPlanner

  parsedQuery:該部分解析了SQL的所有過濾條件

這個部分很好了解,不在過多說明;

  winningPlan:該部分是SQL最終選擇的執行計劃

該部分主要關注stage,stage是執行計劃的類型,有以下分類:

COLLSCAN:全表掃描
IXSCAN:索引掃描
FETCH:根據索引去檢索指定document
SHARD_MERGE:将各個分片傳回資料進行merge
SORT:表明在記憶體中進行了排序
LIMIT:使用limit限制傳回數
SKIP:使用skip進行跳過
IDHACK:針對_id進行查詢           
  • executionStats

    這個部分最好的情況是:nReturned = totalKeysExamined = totalDocsExamined,此部分主要是文檔掃描數以及消耗資訊。

四、索引分析

4.1 基本指令

  • 檢視索引

    MongoDB通過getIndexes()檢視集合的所有索引

db.getCollection('data').getIndexes()
[
    {
        "v" : 2,
        "key" : {
            "_id" : 1
        },
        "name" : "_id_",
        "ns" : "test.data"
    },
    {
        "v" : 2,
        "key" : {
            "name" : 1
        },
        "name" : "name_1",
        "ns" : "test.data"
    }
]           
  • 檢視索引大小

    MongoDB通過totalIndexSize()檢視集合索引的總大小

db.getCollection('data').totalIndexSize()
14389248 //機關位元組           
  • 建立索引
db.collection.createIndex(keys,options)
keys,要建立索引的參數清單。如:{KEY:1/-1},其中key表示字段名,1/-1表示升降序。
options:
    background,在背景建立索引,以便建立索引時不阻止其他資料庫活動。預設值 false。
    unique,建立唯一索引。預設值 false。
    name,指定索引的名稱。如果未指定,MongoDB會生成一個索引字段的名稱和排序順序串聯。
    dropDups,建立唯一索引時,如果出現重複删除後續出現的相同索引,隻保留第一個。
    sparse,對文檔中不存在的字段資料不啟用索引。預設值是 false。
    v,索引的版本号。
    weights,索引權重值,數值在1到99999 之間,表示該索引相對于其他索引字段的得分權重。           
  • 删除索引
db.data.dropIndex("user_1")方法用于删除指定的索引
db.data.dropIndexes()方法用于删除全部的索引           

4.2 索引分類

  • 單字段索引

  單字段索引是針對單個字段進行設定索引的操作

#建立索引文法
db.data.createIndex({name:1})
name:1代表按照升序進行排序,降序排序的索引為-1
db.data.createIndex({name:-1})           
  • 聯合索引

  聯合索引在單字段索引上進行了多個字段操作,将多個字段合并為一個索引的聯合索引,聯合索引需要遵守最左字首原則。

#建立索引文法
db.data.createIndex({name:1,time:1})           
  • 多key索引

  當内容是數組或者list集合建立的一種索引。該索引會為數組中的每個字段建立索引。

  • 子文檔索引

  該索引用來嵌入子文檔中的字段進行建立索引。操作也可以有複合索引,單字段索引。

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

4.2 索引的屬性

  Mongodb支援多類型的索引,還能對索引增加一些額外的屬性。

唯一索引:在Mongodb中_id就是利用單字段索引加唯一索引的屬性構成的
部分索引(3.2版本新增):僅索引符合指定過濾器表達式集合中的文檔。部分索引有較低的存儲要求,降低索引的建立與維護
稀疏索引:確定索引僅包含具有索引字段的文檔的條目,會跳過沒有索引字段的文檔
TTL索引:在一定時間後自動從集合中删除文檔的一種索引           

五、總結

  本文介紹了MongoDB優化的基本思路以及如何檢視慢日志、執行計劃和索引的基本使用,如果有興趣可以針對性的深入研究。