关于mongodb:记某核心MongoDB集群索引优化实践

0次阅读

共计 13544 个字符,预计需要花费 34 分钟才能阅读完成。

腾讯云数据库 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 在腾讯外部及内部的典型利用场景、踩坑案例、性能优化、内核模块化剖析。

正文完
 0