監控Mongo慢查詢


監控Mongo慢查詢

1. 使用mongostat監控MongoDB全局情況

 mongostat是mongdb自帶的狀態檢測工具,在命令行下使用。它會間隔固定時間獲取MongoDB的當前運行狀態,並輸出。
 如果你發現數據庫突然變慢或者有其他問題的話,你第一手的操作就考慮采用mongostat來查看mongo的狀態。
 
 mongostat --host localhost:27017 -uroot -p123456 --authenticationDatabase admin
 參數說明:
 host:指定IP地址和端口,也可以只寫IP,然后使用--port參數指定端口號
 -u: 如果開啟了認證,則需要在其后填寫用戶名
 -p:  密碼
 --authenticationDatabase:若開啟了認證,則需要在此參數后填寫認證庫(注意是認證上述賬號的數據庫)

mongostat輸出詳解

insert/s : 官方解釋是每秒插入數據庫的對象數量,如果是slave,則數值前有*,則表示復制集操作
query/s : 每秒的查詢操作次數
update/s : 每秒的更新操作次數
delete/s : 每秒的刪除操作次數
getmore/s: 每秒查詢cursor(游標)時的getmore操作數
command: 每秒執行的命令數,在主從系統中會顯示兩個值(例如 3|0),分表代表 本地|復制 命令
注: 一秒內執行的命令數比如批量插入,只認為是一條命令(所以意義應該不大)
dirty: 僅僅針對WiredTiger引擎,官網解釋是臟數據字節的緩存百分比
used:僅僅針對WiredTiger引擎,官網解釋是正在使用中的緩存百分比
flushes:
For WiredTiger引擎:指checkpoint的觸發次數在一個輪詢間隔期間
For MMAPv1 引擎:每秒執行fsync將數據寫入硬盤的次數
注:一般都是0,間斷性會是1, 通過計算兩個1之間的間隔時間,可以大致了解多長時間flush一次。flush開銷是很大的,
    如果頻繁的flush,可能就要找找原因了
vsize: 虛擬內存使用量,單位MB (這是 在mongostat 最后一次調用的總數據)
res:  物理內存使用量,單位MB (這是 在mongostat 最后一次調用的總數據)
注:這個和你用top看到的一樣, vsize一般不會有大的變動, res會慢慢的上升,如果res經常突然下降,去查查是否有別的程序狂吃內存。

qr: 客戶端等待從MongoDB實例讀數據的隊列長度
qw: 客戶端等待從MongoDB實例寫入數據的隊列長度
ar: 執行讀操作的活躍客戶端數量
aw: 執行寫操作的活客戶端數量
注:如果這兩個數值很大,那么就是DB被堵住了,DB的處理速度不及請求速度。看看是否有開銷很大的慢查詢。如果查詢一切正常,確實是負載很大,就需要加機器了
netIn:MongoDB實例的網絡進流量
netOut:MongoDB實例的網絡出流量
注:此兩項字段表名網絡帶寬壓力,一般情況下,不會成為瓶頸
conn: 打開連接的總數,是qr,qw,ar,aw的總和
注:MongoDB為每一個連接創建一個線程,線程的創建與釋放也會有開銷,所以盡量要適當配置連接數的啟動參數,
   maxIncomingConnections,阿里工程師建議在5000以下,基本滿足多數場景
set: 副本集的名稱
repl: 節點的復制狀態
 M     ---master
 SEC     ---secondary
 REC     ---recovering
 UNK     ---unknown
 SLV     ---slave
 RTR     ---mongs process("router')
 ARB     ---arbiter

2. 使用Profiling捕捉慢查詢

類似於MySQL的slow log, mongodb可以監控所有慢的以及不慢的查詢。這個工具就是Profiling,該工具在運行的實例上收集有關MongoDB的 寫操作,游標,數據庫命令等,可以在數據庫級別開啟該工具,也可以在實例級別開啟。該工具會把收集到的所有都寫入到system.profile集合中,該集合是一個capped collection。Profiling功能肯定是會影響效率的,但是不太嚴重,原因是他使用的是system.profile 來記錄,而system.profile 是一個capped collection, 這種collection 在操作上有一些限制和特點,但是效率更高。

2.1 慢查詢分析過程

1. 設置一個時間閥值,比如200ms
2. 在profiling中(system.profile)找到超過200ms的語句
3. 查看execStats,分析執行計划
4. 根據分析結果,決定是不是需要添加索引

2.2 Profiling基本操作

mongoshell(或者其他客戶端比如mongochef等
#查看狀態:級別和時間
PRIMARY> db.getProfilingStatus()
{ "was" : 1, "slowms" : 200 }

#查看級別
PRIMARY> db.getProfilingLevel()

#級別說明:
0:關閉,不收集任何數據。
1:收集慢查詢數據,默認是100毫秒。
2:收集所有數據

#設置級別
PRIMARY> db.setProfilingLevel(2)
{ "was" : 1, "slowms" : 100, "ok" : 1 }  #這里返回的是上一次的設置

#設置級別和時間
PRIMARY> db.setProfilingLevel(1,200)
{ "was" : 2, "slowms" : 100, "ok" : 1 }  #這里返回的是上一次的設置

#關閉Profiling
PRIMARY> db.setProfilingLevel(0)
{ "was" : 1, "slowms" : 200, "ok" : 1 }  #這里返回的是上一次的設置


#清空system.profile或者修改大小
#關閉Profiling
PRIMARY> db.setProfilingLevel(0)
{ "was" : 0, "slowms" : 200, "ok" : 1 }
#刪除system.profile集合
PRIMARY> db.system.profile.drop()
true
#創建一個新的system.profile集合 --- 4M
PRIMARY> db.createCollection( "system.profile", { capped: true, size:4000000 } )
{ "ok" : 1 }
#重新開啟Profiling
PRIMARY> db.setProfilingLevel(1,200)
{ "was" : 0, "slowms" : 200, "ok" : 1 }

#如果是復制集環境,要修改副本的system.profile的大小,必須把副本先從復制集中剔除,然后執行上述步驟,最后加入復制集。

#也可以MongoDB啟動時,開啟Profiling   
mongod --profile=1  --slowms=200
#或者在配置文件里添加
profile = 1
slowms = 200

3. 日常使用的Profiling查詢腳本

#返回最近的10條記錄
db.system.profile.find().limit(10).sort({ts:-1}).pretty()
#返回所有的操作,除command類型的
db.system.profile.find({op: {$ne:'command'}}).pretty()
#返回特定集合
db.system.profile.find({ns:'mydb.test'}).pretty()
#返回大於5毫秒慢的操作
db.system.profile.find({millis:{$gt:5}}).pretty()
#從一個特定的時間范圍內返回信息
db.system.profile.find(
                  {
                   ts : {
                         $gt : new ISODate("2015-10-18T03:00:00Z"),
                         $lt : new ISODate("2015-10-19T03:40:00Z")
                        }
                  }
                 ).pretty()
#特定時間,限制用戶,按照消耗時間排序
db.system.profile.find(
                  {
                    ts : {
                          $gt : newISODate("2015-10-12T03:00:00Z") ,
                          $lt : newISODate("2015-10-12T03:40:00Z")
                         }
                  },
                  { user : 0 }
                 ).sort( { millis : -1 } )
#查看最新的 Profile  記錄: 
db.system.profile.find().sort({$natural:-1}).limit(1)
# 顯示5個最近的事件
show profile

4. 案例分析

4.1 獲取慢查詢

#下面的語句過濾幾個大表,因為基本無法優化,需要開發改邏輯,所以做了排除,在輸出方面只輸出了個人認為重要的,方便分析迅速定位

db.system.profile.find({"ns":{"$not":{"$in":["F10data3.f10_4_4_1_gsgg_content", "F10data3.f10_5_1_1_gsyb_content"]}}}, {"ns":1,"op":1, "query":1,"keysExamined":1,"docsExamined":1,"numYield":1, "planSummary":1,"responseLength":1,"millis":1,"execStats":1}).limit(10).sort({ts:-1}).pretty()

#下面是一個超過200ms的查詢語句
{ 
"op" : "query",                     #操作類型,有insert、query、update、remove、getmore、command  
"ns" : "F10data3.f10_2_8_3_jgcc", 
"query" : {                         #具體的查詢語句 包括過濾條件,limit行數  排序字段
    filter" : {
        "jzrq" : {
            "$gte" : ISODate("2017-03-31T16:00:00.000+0000"), 
            "$lte" : ISODate("2017-06-30T15:59:59.000+0000")
        }, 
        "jglxfldm" : 10.0
    }, 
    "ntoreturn" : 200.0,    
    "sort" : {                      #如果有排序  則顯示排序的字段 這里是 RsId
        "RsId" : 1.0
    }
}, 
"keysExamined" : 0.0,               #索引掃描數量 這里是全表掃描,沒有用索引 所以是 0
"docsExamined" : 69608.0,           #瀏覽的文檔數 這里是全表掃描 所以是整個collection中的全部文檔數
"numYield" : 546.0,                 #該操作為了使其他操作完成而放棄的次數。通常來說,當他們需要訪問
                                    還沒有完全讀入內存中的數據時,操作將放棄。這使得在MongoDB為了
                                    放棄操作進行數據讀取的同時,還有數據在內存中的其他操作可以完成。
"locks" : {                         #鎖信息,R:全局讀鎖;W:全局寫鎖;r:特定數據庫的讀鎖;w:特定數據庫的寫鎖
    "Global" : {
        "acquireCount" : {
            "r" : NumberLong(1094)  #該操作獲取一個全局級鎖花費的時間。
        }
    }, 
    "Database" : {
        "acquireCount" : {
            "r" : NumberLong(547)  
        }
    }, 
    "Collection" : {
        "acquireCount" : {
            "r" : NumberLong(547)
        }
    }
}, 
"nreturned" : 200.0,                #返回的文檔數量
"responseLength" : 57695.0,         #返回字節長度,如果這個數字很大,考慮值返回所需字段
"millis" : 264.0,                   #消耗的時間(毫秒)
"planSummary" : "COLLSCAN, COLLSCAN", #執行概覽 從這里看來 是全表掃描 
"execStats" : {                       #詳細的執行計划 這里先略過 后續可以用 explain來具體分析
}, 
"ts" : ISODate("2017-08-24T02:32:49.768+0000"),  #命令執行的時間
"client" : "10.3.131.96",                        #訪問的ip或者主機
"allUsers" : [

], 
"user" : ""
}

4.2 分析慢查詢

 1. 如果發現 millis 值比較大,那么就需要作優化。
 2. 如果docsExamined數很大,或者接近記錄總數(文檔數),那么可能沒有用到索引查詢,而是全表掃描。
 3. 如果keysExamined數為0,也可能是沒用索引。
 4. 結合 planSummary 中的顯示,上例中是  "COLLSCAN, COLLSCAN" 確認是全表掃描
 5. 如果 keysExamined 值高於 nreturned 的值,說明數據庫為了找到目標文檔掃描了很多文檔。這時可以考慮創建索引來提高效率。
 6. 索引的鍵值選擇可以根據 query 中的輸出參考,上例中 filter:包含了 jzrq和jglxfldm 並且按照RsId排序,所以 我們的索引
    索引可以這么建: db.f10_2_8_3_jgcc.ensureindex({jzrq:1,jglxfldm:1,RsId:1})

4.3 執行計划中的TYPE類型

COLLSCAN     #全表掃描                                                避免
IXSCAN       #索引掃描                                                可以改進 選用更高效的索引
FETCH        #根據索引去檢索指定document                               
SHARD_MERGE  #將各個分片返回數據進行merge                               盡可能避免跨分片查詢
SORT         #表明在內存中進行了排序(與老版本的scanAndOrder:true一致)   排序要有index  
LIMIT        #使用limit限制返回數                                      要有限制 Limit+(Fetch+ixscan)最優
SKIP         #使用skip進行跳過                                         避免不合理的skip
IDHACK       #針對_id進行查詢                                          推薦,_id 默認主鍵,查詢速度快
SHARDING_FILTER  #通過mongos對分片數據進行查詢                          SHARDING_FILTER+ixscan最優 
COUNT        #利用db.coll.explain().count()之類進行count運算             
COUNTSCAN    #count不使用Index進行count時的stage返回                    避免 這種情況建議加索引
COUNT_SCAN   #count使用了Index進行count時的stage返回                    推薦
SUBPLA       #未使用到索引的$or查詢的stage返回                           避免
TEXT         #使用全文索引進行查詢時候的stage返回  
PROJECTION   #限定返回字段時候stage的返回                                選擇需要的數據, 推薦PROJECTION+ixscan


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM