記某核心MongoDB集羣索引優化實踐

騰訊雲數據庫MongoDB天然支持高可用、分佈式、高性能、高壓縮、schema free、完善的客戶端訪問均衡策略等功能。雲上某重點用戶基於MongoDB這些優勢,選用MongoDB作爲主存儲服務,該用戶業務場景如下:

· 存儲電商業務核心數據

· 查詢條件多變、查詢不固定,查詢較複雜,查詢組合衆多

· 對性能要求較高

· 對存儲成本有要求

· 流量佔比:insert較少、update較多、find較多、峯值流量較高

· 高峯期讀寫流量數千/秒

通過和業務溝通,瞭解業務使用場景和業務述求後,通過一系列的索引優化,最終完美解決讀寫性能瓶頸問題。本文重點分析該核心業務索引優化過程,通過本文可以學習到以下知識點:

· 如何確定無用索引?

· 如何確定重複索引?

· 如何創建最優索引?

· 對索引的一些錯誤認識?

· 索引優化收益(節省90%以上CPU資源、85%磁盤IO資源、20%存儲成本)

問題分析過程

收到用戶集羣性能瓶頸反饋後,通過集羣監控信息及服務器監控信息可以看出集羣存在如下現象:

· Mongod節點CPU消耗過高,CPU時不時消耗接近90%,甚至100%

· 磁盤IO消耗過高,單節點IO資源消耗佔整服務器60%

· 大量慢日誌(主要集中在find和update),高峯期每秒數千條慢日誌

· 慢日誌類型各不相同,查詢條件衆多

· 所有慢查詢都有匹配到索引

登錄服務器對應節點後臺,獲取慢日誌信息,發現mongod.log中包含大量不同類型find和update的慢日誌,慢日誌都有走索引,任意提取一條慢日誌其內容如下:

Mon Aug 2 10:34:24.928 I COMMAND [conn10480929] command xxx.xxx command: find { find: "xxx", filter: { $and: [ { alxxxId: "xxx" }, { state: 0 }, { itemTagList: { $in: [ xx ] } }, { persxxal: 0 } ] }, limit: 3, maxTimeMS: 10000 } planSummary: IXSCAN { alxxxId: 1.0, itemTagList: 1.0 } keysExamined:1650 docsExamined:1650 hasSortStage:0 cursorExhausted:1 keyUpdates:0 writeConflicts:0 numYields:15 nreturned:3 reslen:8129 locks:{ Global: { acquireCount: { r: 32 } }, Database: { acquireCount: { r: 16 } }, Collection: { acquireCount: { r: 16 } } } protocol:op_command 227ms

Mon Aug 2 10:34:22.965 I COMMAND [conn10301893] command xx.txxx command: find { find: "txxitem", filter: { $and: [ { itxxxId: "xxxx" }, { state: 0 }, { itemTagList: { $in: [ xxx ] } }, { persxxal: 0 } ] }, limit: 3, maxTimeMS: 10000 } planSummary: IXSCAN { alxxxId: 1.0, itemTagList: 1.0 } keysExamined:1498 docsExamined:1498 hasSortStage:0 cursorExhausted:1 keyUpdates:0 writeConflicts:0 numYields:12 nreturned:3 reslen:8039 locks:{ Global: { acquireCount: { r: 26 } }, Database: { acquireCount: { r: 13 } }, Collection: { acquireCount: { r: 13 } } } protocol:op_command 158ms

從上面的日誌打印可以看出,查詢都有走 { alxxxId: 1.0, itemTagList: 1.0 } 索引,走該索引掃描的keysExamined爲1498行,掃描的docsExamined爲1498行,但是返回的doc文檔數卻只有nreturned=3行。

通過上面的日誌核心信息可以看出,滿足條件的數據只有3條,但是卻掃描了1498行數據和索引,說明查詢有走索引,但是不是最優所有。

獲取用戶SQL查詢模型及已有索引信息

上面的分析可以確定問題出現在索引不是最優,大量查詢找了很多無用數據。

3.1. 和用戶接觸,瞭解用戶SQL模型
通過和用戶溝通,收集到用戶查詢、更新主要涉及以下SQL類型:

· 常用查詢、更新類SQL

基於AlxxxId(用戶ID)+itxxxId(單個或多個)
基於AlxxxId查詢count
基於AlxxxId通過時間範圍(createTime)進行分頁查詢,部分查詢會拼接state及其他字段
基於AlxxxId,ParentAlxxxId,parentItxxxId,state組合查詢
基於ItxxxId(單個或多個)查詢數據
基於AlxxxId, state, updateTime組合查詢
基於AlxxxId, state,createTime, totalStock(庫存數量)組合查詢
基於AlxxxId(用戶ID)+itxxxId(單個或多個)+任意其他字段組合
基於AlxxxId, digitalxxxrmarkId(水印ID), state進行查詢
基於AlxxxId, itemTagList(標籤ID),state等進行查詢
基於AlxxxId+itxxxId(單個或多個) +其他任意字段進行查詢
其他查詢

· 統計類count查詢SQL

AlxxxId,state, persxxal 組合
AlxxxId, state,itemType 組合
AlxxxId(用戶ID)+itxxxId(單個或多個)+任意其他字段組合

3.2. 獲取集羣已有索引
通過db.xxx.getindex({})獲取到該表的索引信息如下,總計30個索引:

{ "alxxxId" : 1, "state" : -1, "updateTime" : -1, "itxxxId" : -1, "persxxal" : 1, "srcItxxxId" : -1 }
{ "alxxxId" : 1, "image" : 1 }
{ "itexxxList.vidxxCheck" : 1, "itemType" : 1, "state" : 1 }
{ "alxxxId" : 1, "state" : -1, "newsendTime" : -1, "itxxxId" : 1, "persxxal" : 1 }
{ "_id" : 1 }
{ "alxxxId" : 1, "createTime" : -1, "checkStatus" : 1 }
{ "alxxxId" : 1, "parentItxxxId" : -1, "state" : -1, "updateTime" : -1, "persxxal" : 1, "srcItxxxId" : -1 }
{ "alxxxId" : 1, "state" : -1, "parentItxxxId" : 1, "updateTime" : -1, "persxxal" : -1 }
{ "srcItxxxId" : 1 }
{ "createTime" : 1 }
{ "itexxxList.boyunState" : -1, "itexxxList.wozhituUploadServerId": -1, "itexxxList.photoQiniuUrl" : 1, "itexxxList.sourceType" : 1 }
{ "alxxxId" : 1, "state" : 1, "digitalxxxrmarkId" : 1, "updateTime" : -1 }
{ "itxxxId" : -1 }
{ "alxxxId" : 1, "parentItxxxId" : 1, "parentAlxxxId" : 1, "state" : 1 }
{ "alxxxId" : 1, "videoCover" : 1 }
{ "alxxxId" : 1, "itemType" : 1 }
{ "alxxxId" : 1, "state" : -1, "itemType" : 1, "persxxal" : 1, "updateTime" : 1 }
{ "alxxxId" : 1, "itxxxId" : 1 }
{ "itxxxId" : 1, "alxxxId" : 1 }
{ "alxxxId" : 1, "parentAlxxxId" : 1, "state" : 1 }
{ "alxxxId" : 1, "itemTagList" : 1 }
{ "itexxxList.photoQiniuUrl" : 1, "itexxxList.boyunState" : -1, "itexxxList.sourceType" : 1, "itexxxList.wozhituUploadServerId" : -1 }
{ "alxxxId" : 1, "parentItxxxId" : 1, "state" : 1 }
{ "alxxxId" : 1, "parentItxxxId" : 1, "updateTime" : 1 }
{ "updateTime" : 1 }
{ "itemPhoxxIdList" : -1 }
{ "alxxxId" : 1, "state" : -1, "isTop" : 1 }
{ "alxxxId" : 1, "state" : 1, "itemResxxxIdList" : 1, "updateTime" : -1 }
{ "alxxxId" : 1, "state" : -1, "itexxxList.photoQiniuUrl" : 1 }
{ "itexxxList.qiniuStatus" : 1, "itexxxList.photoNetUrl" : 1, "itexxxList.photoQiniuUrl" : 1 }
{ "itemResxxxIdList" : 1 }

索引優化過程

從上一節可以看出,該集羣查詢複雜,同時索引衆多,通過分析已有索引和用戶數據模型,對已有索引做如下優化,最終有效索引減少到8個。

4.1. 第一輪優化:刪除無用索引
MongoDB默認提供有索引統計命令來獲取各個索引命中的次數,該命令如下:

db.xxxxx.aggregate({"$indexStats":{}})
{ "name" : "alxxxId_1_parentItxxxId_1_parentAlxxxId_1", "key" : { "alxxxId" : 1, "parentItxxxId" : 1, "parentAlxxxId" : 1 },"host" : "TENCENT64.site:7014", "accesses" : { "ops" : NumberLong(11236765),"since" : ISODate("2020-08-17T06:39:43.840Z") } }

該聚合輸出中的幾個核心指標信息如下表:

字段內容

說明

name

索引名,代表是針對那個索引的統計。

ops

索引命中次數,也就是所有查詢中採用本索引作爲查詢索引的次數。

上表中的ops代表命中次數,如果命中次數爲0或者很小,說明該索引很少被選爲最優索引使用,因此可以任務是無用索引,可以直接刪除。

獲取用戶核心表索引統計信息,如下:

db.xxx.aggregate({"$indexStats":{}})
{ "alxxxId" : 1, "state" : -1, "updateTime" : -1, "itxxxId" : -1, "persxxal" : 1, "srcItxxxId" : -1 } "ops" : NumberLong(88518502)
{ "alxxxId" : 1, "image" : 1 } "ops" : NumberLong(293104)
{ "itexxxList.vidxxCheck" : 1, "itemType" : 1, "state" : 1 } "ops" : NumberLong(0)
{ "alxxxId" : 1, "state" : -1, "newsendTime" : -1, "itxxxId" : -1, "persxxal" : 1 } "ops" : NumberLong(33361216)
{ "_id" : 1 } "ops" : NumberLong(3987)
{ "alxxxId" : 1, "createTime" : 1, "checkStatus" : 1 } "ops" : NumberLong(20042796)
{ "alxxxId" : 1, "parentItxxxId" : -1, "state" : -1, "updateTime" : -1, "persxxal" : 1, "srcItxxxId" : -1 } "ops" : NumberLong(43042796)
{ "alxxxId" : 1, "state" : -1, "parentItxxxId" : 1, "updateTime" : -1, "persxxal" : -1 } "ops" : NumberLong(3042796)
{ "itxxxId" : -1 } "ops" : NumberLong(38854593)
{ "srcItxxxId" : -1 } "ops" : NumberLong(0)
{ "createTime" : 1 } "ops" : NumberLong(62)
{ "itexxxList.boyunState" : -1, "itexxxList.wozhituUploadServerId" : -1, "itexxxList.photoQiniuUrl" : 1, "itexxxList.sourceType" : 1 } "ops" : NumberLong(0)
{ "alxxxId" : 1, "state" : 1, "digitalxxxrmarkId" : 1, "updateTime" : -1 } "ops" : NumberLong(140238342)
{ "itxxxId" : -1 } "ops" : NumberLong(38854593)
{ "alxxxId" : 1, "parentItxxxId" : 1, "parentAlxxxId" : 1, "state" : 1 } "ops" : NumberLong(132237254)
{ "alxxxId" : 1, "videoCover" : 1 } { "ops" : NumberLong(2921857)
{ "alxxxId" : 1, "itemType" : 1 } { "ops" : NumberLong(457)
{ "alxxxId" : 1, "state" : -1, "itemType" : 1, "persxxal" : 1, " itxxxId " : 1 } "ops" : NumberLong(68730734)
{ "alxxxId" : 1, "itxxxId" : 1 } "ops" : NumberLong(232360252)
{ "itxxxId" : 1, "alxxxId" : 1 } "ops" : NumberLong(145640252)
{ "alxxxId" : 1, "parentAlxxxId" : 1, "state" : 1 } "ops" : NumberLong(689891)
{ "alxxxId" : 1, "itemTagList" : 1 } "ops" : NumberLong(2898693682)
{ "itexxxList.photoQiniuUrl" : 1, "itexxxList.boyunState" : 1, "itexxxList.sourceType" : 1, "itexxxList.wozhituUploadServerId" : 1 } "ops" : NumberLong(511303207)
{ "alxxxId" : 1, "parentItxxxId" : 1, "state" : 1 } "ops" : NumberLong(0)
{ "alxxxId" : 1, "parentItxxxId" : 1, "updateTime" : 1 } "ops" : NumberLong(0)
{ "updateTime" : 1 } "ops" : NumberLong(1397)
{ "itemPhoxxIdList" : -1 } "ops" : NumberLong(0)
{ "alxxxId" : 1, "state" : -1, "isTop" : 1 } "ops" : NumberLong(213305)
{ "alxxxId" : 1, "state" : 1, "itemResxxxIdList" : 1, "updateTime" : 1 } "ops" : NumberLong(2591780)
{ "alxxxId" : 1, "state" : 1, "itexxxList.photoQiniuUrl" : 1} "ops" : NumberLong(23505)
{ "itexxxList.qiniuStatus" : 1, "itexxxList.photoNetUrl" : 1, "itexxxList.photoQiniuUrl" : 1 } "ops" : NumberLong(0)
{ "itemResxxxIdList" : 1 } "ops" : NumberLong(7)

該業務已經運行一段時間,首先把ops小於10000的索引刪除,滿足該條件的索引如上面的紅色部分索引。

通過該輪刪除11個無用索引後,剩餘有用索引30-11=19個索引。

4.2. 第二輪優化:刪除重複索引
重複索引主要包括以下幾類:

· 查詢順序引起的索引重複

例如該業務不同開發寫了如下兩種查詢:

db.xxxx.find({{ "alxxxId" : xxx, "itxxxId" : xxx }})
db.xxxx.find({{ " itxxxId " : xxx, " alxxxId " : xxx }})

爲了應對這兩種查詢SQL,DBA創建了兩個索引:

{ alxxxId :1, itxxxId:1 }和{ itxxxId :1, alxxxId:1}

這兩個SQL實際上創建任何一個索引即可,無需兩個索引,因此這就是無用索引。

· 最左原則匹配引起的索引重複

例如下面的三個索引存在重複索引:

{ itxxxId:1, alxxxId:1 }和{ itxxxId :1}這兩個索引,{ itxxxId :1}即爲重複索引。

· 包含關係引起的索引重複

例如上面的以下兩個索引爲重複索引:

{ "alxxxId" : 1, "parentItxxxId" : 1, "parentAlxxxId" : 1, "state" : 1 }
{ "alxxxId" : 1, "parentAlxxxId" : 1, "state" : 1 }
{ "alxxxId" : 1, " state " : 1 }

和用戶確認,用戶創建這三個索引,是因爲有如下三個查詢:

Db.xxx.find({ "alxxxId" : xxx, "parentItxxxId" : xx, "parentAlxxxId" : xxx, "state" : xxx })
Db.xxx.find({ "alxxxId" : xxx, " parentAlxxxId " : xx, " state " : xxx })
Db.xxx.find({ "alxxxId" : xxx, " state " : xxx })

這幾個查詢都包含公共字段,因此可以合併爲一個索引來滿足這兩類SQL的查詢,合併後的索引如下:

{ "alxxxId" : 1, " state " : 1, " parentAlxxxId " : 1, parentItxxxId :1}

通過以上幾個索引原則優化後,以下幾個索引可以優化,優化前索引:

{ itxxxId:1, alxxxId:1 }
{ alxxxId:1, itxxxId:1 }
{itxxxId:1 }
{ "alxxxId" : 1, "parentItxxxId" : 1, "parentAlxxxId" : 1, "state" : 1 }
{ "alxxxId" : 1, "parentAlxxxId" : 1, "state" : 1 }
{ "alxxxId" : 1, " state " : 1 }

這6個索引可以合併優化爲如下2個索引:

{ itxxxId:1, alxxxId:1 }
{ "alxxxId" : 1, "parentItxxxId" : 1, "parentAlxxxId" : 1, "state" : 1 }

4.3. 第三輪優化:獲取數據模型,剔除唯一索引引起的無用索引

通過分析表中數據各個字段模塊組合,發現alxxxId和itxxxId字段爲高頻字段,通過分析字段schema信息,隨機抽取一部分數據,發現這兩個字段組合是唯一的。於是和用戶確認,用戶反饋這兩個字段的任意組合都代表一條唯一的數據。

如果{alxxxId:1, itxxxId:1}索引可以確定唯一性,則這兩個字段和任何字段的組合都是唯一的。因此下面的幾個索引可以合併爲一個索引:

{ "alxxxId" : 1, "state" : -1, "updateTime" : -1, "itxxxId" : 1, "persxxal" : 1, "srcItxxxId" : -1 }
{ "alxxxId" : 1, "state" : -1, "itemType" : 1, "persxxal" : 1, " itxxxId " : 1 }
{ "alxxxId" : 1, "state" : -1, "newsendTime" : -1, "itxxxId" : 1, "persxxal" : 1 }
{ "alxxxId" : 1, "state" : 1, "itxxxId" : 1, "updateTime" : -1 }
{ itxxxId:1, alxxxId:1 }

上面的5個索引可以去重合併爲以下一個索引:{ itxxxId:1, alxxxId:1 }

4.4. 第四輪優化:非等值查詢引起的無用重複索引優化
從前面的30個索引可以看出,索引中有部分爲時間類型字段,如createTime、updateTime,這類字段一般用於範圍查詢,通過和用戶確認,這些字段確實用於各種範圍查詢。由於範圍查詢屬於非等值查詢,如果範圍查詢字段出現在索引字段前面,則後面字段無法走索引,例如如下查詢及索引:

db.collection.find({{ "alxxxId" : xx, "parentItxxxId" : xx, "state" : xx, "updateTime" : {$gt: xxxxx}, "persxxal" : xxx, "srcItxxxId" : xxx } })

db.collection.find({{ "alxxxId" : xx, "state" : xx, "parentItxxxId" : xx, "updateTime" : {$lt: xxxxx}, "persxxal" : xxx} })

用戶爲這兩個查詢增加了以下兩個索引:

第一個索引如下:
{ "alxxxId" : 1, "parentItxxxId" : -1, "state" : -1, "updateTime" : -1, "persxxal" : 1, "srcItxxxId" : -1 }
第二個索引如下:
{ "alxxxId" : 1, "state" : -1, "parentItxxxId" : 1, "updateTime" : -1, "persxxal" : -1 }

由於這兩個查詢都包含updateTime字段,並進行範圍查詢。由於除了updateTime字段以外的字段都是等值查詢,因此上面兩個查詢實際上updateTime右邊的字段無法走索引。也就是上面的第一個索引persxxal和srcItxxxId字段無法匹配索引,第二個索引persxxal字段無法匹配索引。

同時由於這兩個索引字段基本相同,爲了更好保證更多字段走索引,因此可以合併優化爲如下一個索引,確保更多字段能夠走索引:

{ "alxxxId" : 1, "state" : -1, "parentItxxxId" : 1, "persxxal" : -1, "updateTime" : -1 }

4.5. 第五輪優化:去除查詢頻率較低的字段對應索引

第一輪優化刪除無用索引的時候,過濾掉了命中率低於10000次以下的索引。但是,還有一部分索引相比高頻命中次數(數十億次)命中次數也相對較低(命中次數只有幾十萬),這部分較低頻命中次數的索引如下:

{ "alxxxId" : 1, "image" : 1 } "ops" : NumberLong(293104)

{ "alxxxId" : 1, "videoCover" : 1 } "ops" : NumberLong(292857)

調低慢日誌時延閾值,分析這兩個查詢對應日誌,如下:

Mon Aug 2 10:56:46.533 I COMMAND [conn5491176] command xxxx.tbxxxxx command: count { count: "xxxxx", query: { alxxxId: "xxxxxx", itxxxId: "xxxxx", image: "http:/xxxxxxxxxxx/xxxxx.jpg" }, limit: 1 } planSummary: IXSCAN { itxxxId: 1.0,alxxxId:1.0 } keyUpdates:0 writeConflicts:0 numYields:1 reslen:62 locks:{ Global: { acquireCount: { r: 4 } }, Database: { acquireCount: { r: 2 } }, Collection: { acquireCount: { r: 2 } } } protocol:op_query 4ms

Mon Aug 2 10:47:53.262 I COMMAND [conn10428265] command xxxx.tbxxxxx command: find { find: "xxxxx", filter: { $and: [ { alxxxId: "xxxxxxx" }, { state: 0 }, { itemTagList: { $size: 0 } } ] }, limit: 1, singleBatch: true } planSummary: IXSCAN { alxxxId: 1, videoCover: 1 } keysExamined:128 docsExamined:128 cursorExhausted:1 keyUpdates:0 writeConflicts:0 numYields:22 nreturned:0 reslen:108 locks:{ Global:{ acquireCount: { r: 46 } }, Database: { acquireCount: { r: 23 } }, Collection: { acquireCount: { r: 23 } } } protocol:op_command 148ms

分析日誌發現用戶請求中的image都是和alxxxId,itxxxId進行組合查詢,前面提到alxxxId,itxxxId是唯一的,從查詢計劃也可以看出,image字段完全沒有走索引。因此{ "alxxxId" : 1, "ixxxge" : 1 }索引可以刪除。

同理,分析日誌發現用戶查詢條件中沒有攜帶videoCover,只是部分查詢走了{ alxxxId: 1, videoCover: 1 } 索引,並且keysExamined、docsExamined與nreturned不相同,所以可以確認實際只匹配了alxxxId索引字段。因此,該索引{ alxxxId: 1, videoCover: 1 } 可以刪除。

4.6. 第六輪優化:分析日誌高頻查詢,添加高頻查詢最優索引
調低日誌閾值,通過mtools工具分析一段時間的查詢,獲取到如下熱點查詢信息:

這部分高頻熱點查詢幾乎佔用了99%以上的查詢,因此務必確保這部分查詢需要所有字段能走索引。分析該類查詢對應日誌,得到如下信息:

Mon Aug 2 10:47:58.015 I COMMAND [conn4352017] command xxxx.xxx command: find { find: "xxxxx", filter: { $and: [ { alxxxId:"xxxxx" }, { state: 0 }, { itemTagList: { $in: [ xxxxx ] } }, { persxxal: 0 } ] }, projection: { $sortKey: { $meta: "sortKey" } }, sort: { updateTime: 1 }, limit: 3, maxTimeMS: 10000 } planSummary: IXSCAN { alxxxId: 1.0, itexxagList: 1.0 } keysExamined:1327 docsExamined:1327 hasSortStage:1 cursorExhausted:1 keyUpdates:0 writeConflicts:0 numYields:23 nreturned:3 reslen:12036 locks:{ Global: { acquireCount: { r: 48 } }, Database: { acquireCount: { r: 24 } }, Collection: { acquireCount: { r: 24 } } } protocol:op_command 151ms

上面日誌可以看出,該高頻查詢掃描數據行數和最終返回的數據行數差距很大,掃描了1327行,最終只獲取到了3條數據,走的是 { alxxxId: 1.0, itexxagList: 1.0 }

索引,該索引不是最優索引。該高頻查詢是四字段的等值查詢,只有兩個字段走了索引,可以把該索引優化爲如下索引:{ alxxxId: 1.0, itexxagList: 1.0 , persxxal:1.0, stat:1.0}

此外,從日誌可以看出,該高頻查詢實際上還有個sort排序和limit限制,整個查詢原始SQL如下:

db.xxx.find({ $and: [ { alxxxId:"xxxx" }, { state: 0 }, { itexxagList: { $in: [ xxxx ] } },{ persxxal: 0 } ] }).sort({updateTime:1}).limit(3)

該查詢模型爲普通多字段等值查詢 + sort排序類查詢 + limit限制。該類查詢最優索引可能是下面兩個索引中的一個:

· 索引1:普通多字段等值查詢對應索引

對應查詢中的如下SQL查詢條件:

{ $and: [ { alxxxId:"xxx" }, { state: 0 }, { itexxagList: { $in: [ xxxx ] } }, { persxxal: 0 } ] }

該SQL四個字段都爲等值查詢,按照散列度創建最優索引,取值越散列的字段放最左邊,可以得到如下最優索引:

{ alxxxId: 1.0, itexxagList: 1.0 , persxxal:1.0, stat:1.0}

如果選擇該索引作爲最優索引,則整個普通多字段等值查詢 + sort排序類查詢 + limit限制查詢執行流程如下:

  1. 通過{ alxxxId: 1.0, itexxagList: 1.0 , persxxal:1.0, stat:1.0}索引找出滿足{ $and: [ { alxxxId:"xxxx" }, { state: 0 }, { itexxagList: { $in: [ xxxx ] } }, { persxxal: 0 } ] }條件的所有數據。

  2. 對這些滿足條件的數據進行內存排序

  3. 取排序好的前三條數據

· 索引2:Sort排序對應最優索引

由於查詢中帶有limit,因此有可能直接走{updateTime:1}排序索引,通過該索引找出三條滿足以下查詢條件的數據:

{ $and: [ { alxxxId:"xxxx" }, { state: 0 }, { itexxagList: { $in: [ xxxx ] } },
{ persxxal: 0 } ] }

整個普通多字段等值查詢 + sort排序類查詢 + limit限制查詢對應索引選擇索引1和索引2和數據分佈有較大的關係,由於該查詢爲超高頻查詢,因此建議這類SQL添加2個索引,由MongoDB內核根據實際查詢條件和數據分佈自己決定選擇那個索引作爲最優索引,該高頻查詢對應索引如下:

{ alxxxId: 1.0, itexxagList: 1.0 , persxxal:1.0, stat:1.0}
{updateTime:1}

4.7. 小結

通過前面六輪優化後,最終只保留如下8個索引:

{ "itxxxId" : 1, "alxxxId" : 1 }
{ "alxxxId" : 1, "state" : 1, "digitalxxxrmarkId" : 1, "updateTime" : 1 }
{ "alxxxId" : 1, "state" : -1, "parentItxxxId" : 1, "persxxal" : -1, "updateTime" : 1 }
{ "alxxxId" : 1, "itexxxList.photoQiniuUrl" : 1, }
{ "alxxxId" : 1, "parentAlxxxId" : 1, "state" : 1"parentItxxxId" : 1}
{ alxxxId: 1.0, itexxagList: 1.0 , persxxal:1.0, stat:1.0}
{updateTime:1}
{ "alxxxId" : 1,"createTime" : -1}

索引優化收益

通過一系列索引優化後,最終索引減少到8個,整體收益非常明細,主要收益如下:

· 節省90% 以上CPU資源

峯值CPU消耗從之前的90%多降到優化後的10%以內

· 節省85% 左右磁盤IO資源

磁盤IO消耗從之前的60%-70%降低到10%以內

· 節省20%磁盤存儲成本

由於每個索引都對應一個磁盤索引文件,索引從30個減少到8個,數據+索引最終真實磁盤消耗減少20%左右。

· 慢日誌減少99%

索引優化前慢日誌每秒數千條,優化後慢日誌條數降低到數十條。優化由慢日誌主要有求count引起,滿足條件數據太多,這是正常現象。

最後,索引對MongoDB數據庫查詢性能起着至關重要的作用,用最少索引滿足用戶查詢需求會極大提升數據庫性能,並減少存儲成本。騰訊雲DBbrain for MongoDB基於SQL分類+索引規則+代價計算完美實現索引智能推薦,下期將爲大家帶來騰訊雲索引推薦方案及實現細節的分享。

作者:騰訊雲MongoDB團隊
騰訊雲MongoDB當前服務於遊戲、電商、社交、教育、新聞資訊、金融、物聯網、軟件服務等多個行業;MongoDB團隊(簡稱CMongo)致力於對開源MongoDb內核進行深度研究及持續性優化(如百萬庫表、物理備份、免密、審計等),爲用戶提供高性能、低成本、高可用性的安全數據庫存儲服務。後續持續分享MongoDb在騰訊內部及外部的典型應用場景、踩坑案例、性能優化、內核模塊化分析。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章