我司商品服務
的MongoDB主庫
曾出現過嚴重抖動、頻繁鎖庫等狀況。MongoDB
、而後當即查詢等邏輯,所以項目並未開啓讀寫分離。慢查詢
致使MongoDB慢查詢
的監控和告警幸運的一點:在出事故以前恰好完成了緩存過時時間的升級且過時時間爲一個月,C端查詢
都落在緩存上,所以沒有形成P0級
事故,僅僅阻塞了部分B端邏輯
<br/>數據庫
我司的各類監控作的比較到位,當天忽然收到了數據庫服務器負載較高的告警通知,因而我和同事們就趕忙登陸了Zabbix監控
,以下圖所示,截圖的時候是正常狀態,當時事故期間忘記留圖了,能夠想象當時的數據曲線反正是該高的很低,該低的很高就是了。緩存
Zabbix 分佈式監控系統官網: https://www.zabbix.com/
<br/>bash
咱們研發是沒有操控服務器權限的,所以委託運維同窗幫助咱們抓取了部分查詢記錄,以下所示:服務器
---------------------------------------------------------------------------------------------------------------------------+ Op | Duration | Query ---------------------------------------------------------------------------------------------------------------------------+ query | 5 s | {"filter": {"orgCode": 350119, "fixedStatus": {"$in": [1, 2]}}, "sort": {"_id": -1}, "find": "sku_main"} query | 5 s | {"filter": {"orgCode": 350119, "fixedStatus": {"$in": [1, 2]}}, "sort": {"_id": -1}, "find": "sku_main"} query | 4 s | {"filter": {"orgCode": 346814, "fixedStatus": {"$in": [1, 2]}}, "sort": {"_id": -1}, "find": "sku_main"} query | 4 s | {"filter": {"orgCode": 346814, "fixedStatus": {"$in": [1, 2]}}, "sort": {"_id": -1}, "find": "sku_main"} query | 4 s | {"filter": {"orgCode": 346814, "fixedStatus": {"$in": [1, 2]}}, "sort": {"_id": -1}, "find": "sku_main"} ...
查詢很慢的話全部研發應該第一時間想到的就是索引
的使用問題,因此當即檢查了一遍索引,以下所示:運維
### 當時的索引 db.sku_main.ensureIndex({"orgCode": 1, "_id": -1},{background:true}); db.sku_main.ensureIndex({"orgCode": 1, "upcCode": 1},{background:true}); ....
我屏蔽了干擾項,反正能很明顯的看出來,這個查詢是徹底能夠命中索引的,因此就須要直面第一個問題:分佈式
<font color="red">上述查詢記錄中排首位的慢查詢究竟是不是出問題的根源?</font>post
個人判斷是:它應該不是數據庫總體緩慢的根源,由於第一它的查詢條件足夠簡單暴力,徹底命中索引,在索引之上有一點其餘的查詢條件而已,第二在查詢記錄中也存在相同結構不一樣條件的查詢,耗時很是短。學習
在運維同窗繼續排查查詢日誌時,發現了另外一個比較驚爆的查詢,以下:優化
### 當時場景日誌 query: { $query: { shopCategories.0: { $exists: false }, orgCode: 337451, fixedStatus: { $in: [ 1, 2 ] }, _id: { $lt: 2038092587 } }, $orderby: { _id: -1 } } planSummary: IXSCAN { _id: 1 } ntoreturn:1000 ntoskip:0 keysExamined:37567133 docsExamined:37567133 cursorExhausted:1 keyUpdates:0 writeConflicts:0 numYields:293501 nreturned:659 reslen:2469894 locks:{ Global: { acquireCount: { r: 587004 } }, Database: { acquireCount: { r: 293502 } }, Collection: { acquireCount: { r: 293502 } } } # 耗時 179530ms
耗時180秒且基於查詢的執行計劃
能夠看出,它走的是_id_
索引,進行了全表掃描,掃描的數據總量爲:37567133,不慢纔怪。ui
<br/>
定位到問題後,沒辦法當即修改,第一要務是:止損
結合當時的時間也比較晚了,所以咱們發了公告,禁止了上述查詢的功能並短暫暫停了部分業務,,過了一會以後進行了主從切換
,再去看Zabbix監控
就一切安好了。
<br/>
咱們回顧一下查詢的語句和咱們預期的索引,以下所示:
### 原始Query db.getCollection("sku_main").find({ "orgCode" : NumberLong(337451), "fixedStatus" : { "$in" : [ 1.0, 2.0 ] }, "shopCategories" : { "$exists" : false }, "_id" : { "$lt" : NumberLong(2038092587) } } ).sort( { "_id" : -1.0 } ).skip(1000).limit(1000); ### 指望的索引 db.sku_main.ensureIndex({"orgCode": 1, "_id": -1},{background:true});
乍一看,好像一切都很Nice啊,字段orgCode
等值查詢,字段_id
按照建立索引的方向進行倒序排序,爲啥會這麼慢?
可是,關鍵的一點就在 $lt
上
在MongoDB中,排序操做能夠經過從索引中按照索引的順序獲取文檔的方式,來保證結果的有序性。
若是MongoDB的查詢計劃器無法從索引中獲得排序順序,那麼它就須要在內存中對結果排序。
注意:不用索引的排序操做,會在內存超過32MB時終止,也就是說MongoDB只能支持32MB之內的非索引排序
不管是MongoDB仍是MySQL都是用的樹結構做爲索引,若是排序方向
和索引方向
相反,只須要從另外一頭開始遍歷便可,以下所示:
# 索引 db.records.createIndex({a:1}); # 查詢 db.records.find().sort({a:-1}); # 索引爲升序,可是我查詢要按降序,我只須要從右端開始遍歷便可知足需求,反之亦然 MIN 0 1 2 3 4 5 6 7 MAX
官方介紹:MongoDB supports compound indexes, where a single index structure holds references to multiple fields within a collection’s documents.
複合索引結構示意圖以下所示:
該索引恰好和咱們討論的是同樣的,userid順序
,score倒序
。
咱們須要直面第二個問題:<font color="red">複合索引在使用時需不須要在意方向?</font>
假設兩個查詢條件:
# 查詢 一 db.getCollection("records").find({ "userid" : "ca2" }).sort({"score" : -1.0}); # 查詢 二 db.getCollection("records").find({ "userid" : "ca2" }).sort({"score" : 1.0});
上述的查詢沒有任何問題,由於受到score
字段排序的影響,只是數據從左側仍是從右側遍歷的問題,那麼下面的一個查詢呢?
# 錯誤示範 db.getCollection("records").find({ "userid" : "ca2", "score" : { "$lt" : NumberLong(2038092587) } }).sort({"score" : -1.0});
錯誤緣由以下:
仔細閱讀了根源以後,再回顧線上的查詢語句,以下:
### 原始Query db.getCollection("sku_main").find({ "orgCode" : NumberLong(337451), "fixedStatus" : { "$in" : [ 1.0, 2.0 ] }, "shopCategories" : { "$exists" : false }, "_id" : { "$lt" : NumberLong(2038092587) } } ).sort( { "_id" : -1.0 } ).skip(1000).limit(1000); ### 指望的索引 db.sku_main.ensureIndex({"orgCode": 1, "_id": -1},{background:true});
犯的錯誤如出一轍,因此MongoDB
放棄了複合索引的使用,該爲單列索引,所以進行鍼對性修改,把 $lt
條件改成 $gt
觀察優化結果:
# 原始查詢 [TEMP INDEX] => lt: {"limit":1000,"queryObject":{"_id":{"$lt":2039180008},"categoryId":23372,"orgCode":351414,"fixedStatus":{"$in":[1,2]}},"restrictedTypes":[],"skip":0,"sortObject":{"_id":-1}} # 原始耗時 [TEMP LT] => 超時 (超時時間10s) # 優化後查詢 [TEMP INDEX] => gt: {"limit":1000,"queryObject":{"_id":{"$gt":2039180008},"categoryId":23372,"orgCode":351414,"fixedStatus":{"$in":[1,2]}},"restrictedTypes":[],"skip":0,"sortObject":{"_id":-1}} # 優化後耗時 [TEMP GT] => 耗時: 383ms , List Size: 999
分析了小2000字,其實改動就是兩個字符而已,固然真正的改動須要考慮業務的須要,可是問題既然已經定位,修改什麼的就不難了,回顧上述內容總結以下:
MongoDB數據庫複合索引在使用中必定要注意其方向
,要徹底理解其邏輯,避免索引失效若是你以爲這篇內容對你挺有幫助的話:
再來看看最近幾篇的「查漏補缺」系列吧,該系列會持續輸出~