我司商品服务
的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数据库复合索引在使用中必定要注意其方向
,要彻底理解其逻辑,避免索引失效若是你以为这篇内容对你挺有帮助的话:
再来看看最近几篇的「查漏补缺」系列吧,该系列会持续输出~