服务器之家:专注于服务器技术及软件下载分享
分类导航

Mysql|Sql Server|Oracle|Redis|MongoDB|PostgreSQL|Sqlite|DB2|mariadb|Access|数据库技术|

服务器之家 - 数据库 - MongoDB - 「生产事故」MongoDB复合索引引发的灾难

「生产事故」MongoDB复合索引引发的灾难

2023-05-07 04:14未知服务器之家 MongoDB

[[356516]] 前情提要 11月末我司商品服务的MongoDB主库曾出现过严重抖动、频繁锁库等情况。 由于诸多业务存在插入MongoDB、然后立即查询等逻辑,因此项目并未开启读写分离。 最终定位问题是由于:服务器自身磁盘 + 大量慢查询导致

[[356516]]

前情提要

11月末我司商品服务的MongoDB主库曾出现过严重抖动、频繁锁库等情况。

由于诸多业务存在插入MongoDB、然后立即查询等逻辑,因此项目并未开启读写分离。

最终定位问题是由于:服务器自身磁盘 + 大量慢查询导致

基于上述情况,运维同学后续着重增强了对MongoDB慢查询的监控和告警

幸运的一点:在出事故之前刚好完成了缓存过期时间的升级且过期时间为一个月,C端查询都落在缓存上,因此没有造成P0级事故,仅仅阻塞了部分B端逻辑

事故回放

我司的各种监控做的比较到位,当天突然收到了数据库服务器负载较高的告警通知,于是我和同事们就赶紧登录了Zabbix监控,如下图所示,截图的时候是正常状态,当时事故期间忘记留图了,可以想象当时的数据曲线反正是该高的很低,该低的很高就是了。

Zabbix 分布式监控系统官网:http://www.zzvips.com/uploads/allimg/mbient3qsiv

「生产事故」MongoDB复合索引引发的灾难

开始分析

我们研发是没有操控服务器权限的,因此委托运维同学帮助我们抓取了部分查询记录,如下所示:

  1. ---------------------------------------------------------------------------------------------------------------------------+ 
  2. Op          | Duration | Query                                                                                                                   ---------------------------------------------------------------------------------------------------------------------------+ 
  3. query       | 5 s      | {"filter": {"orgCode": 350119, "fixedStatus": {"$in": [1, 2]}}, "sort": {"_id": -1}, "find""sku_main"}                
  4. 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"
  5. ... 

查询很慢的话所有研发应该第一时间想到的就是索引的使用问题,所以立即检查了一遍索引,如下所示:

  1. ### 当时的索引 
  2.  
  3. db.sku_main.ensureIndex({"orgCode": 1, "_id": -1},{background:true}); 
  4. db.sku_main.ensureIndex({"orgCode": 1, "upcCode": 1},{background:true}); 
  5. .... 

我屏蔽了干扰项,反正能很明显的看出来,这个查询是完全可以命中索引的,所以就需要直面第一个问题:

上述查询记录中排首位的慢查询到底是不是出问题的根源?

我的判断是:它应该不是数据库整体缓慢的根源,因为第一它的查询条件足够简单暴力,完全命中索引,在索引之上有一点其他的查询条件而已,第二在查询记录中也存在相同结构不同条件的查询,耗时非常短。

在运维同学继续排查查询日志时,发现了另一个比较惊爆的查询,如下:

  1. ### 当时场景日志 
  2.  
  3. 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 } } }  
  4.  
  5. # 耗时 
  6. 179530ms 

# 耗时耗时180秒且基于查询的执行计划可以看出,它走的是_id_索引,进行了全表扫描,扫描的数据总量为:37567133,不慢才怪。

迅速解决

定位到问题后,没办法立即修改,第一要务是:止损

结合当时的时间也比较晚了,因此我们发了公告,禁止了上述查询的功能并短暂暂停了部分业务,,过了一会之后进行了主从切换,再去看Zabbix监控就一切安好了。

分析根源

我们回顾一下查询的语句和我们预期的索引,如下所示:

  1. ### 原始Query 
  2. db.getCollection("sku_main").find({  
  3.         "orgCode" : NumberLong(337451),  
  4.         "fixedStatus" : {  
  5.             "$in" : [ 
  6.                 1.0,  
  7.                 2.0 
  8.             ] 
  9.         },  
  10.         "shopCategories" : {  
  11.             "$exists" : false 
  12.         },  
  13.         "_id" : {  
  14.             "$lt" : NumberLong(2038092587) 
  15.         } 
  16.     } 
  17. ).sort( 
  18.     {  
  19.         "_id" : -1.0 
  20.     } 
  21. ).skip(1000).limit(1000); 
  22.  
  23. ### 期望的索引 
  24. db.sku_main.ensureIndex({"orgCode": 1, "_id": -1},{background:true}); 

乍一看,好像一切都很Nice啊,字段orgCode等值查询,字段_id按照创建索引的方向进行倒序排序,为啥会这么慢?

但是,关键的一点就在 $lt 上

知识点一:索引、方向及排序

在MongoDB中,排序操作可以通过从索引中按照索引的顺序获取文档的方式,来保证结果的有序性。

如果MongoDB的查询计划器没法从索引中得到排序顺序,那么它就需要在内存中对结果排序。

注意:不用索引的排序操作,会在内存超过32MB时终止,也就是说MongoDB只能支持32MB以内的非索引排序

知识点二:单列索引不在乎方向

无论是MongoDB还是MySQL都是用的树结构作为索引,如果排序方向和索引方向相反,只需要从另一头开始遍历即可,如下所示:

  1. # 索引 
  2. db.records.createIndex({a:1});  
  3.  
  4. # 查询 
  5. db.records.find().sort({a:-1}); 
  6.  
  7. # 索引为升序,但是我查询要按降序,我只需要从右端开始遍历即可满足需求,反之亦然 
  8. MIN 0 1 2 3 4 5 6 7 MAX 

MongoDB的复合索引结构

官方介绍:MongoDB supports compound indexes, where a single index structure holds references to multiple fields within a collection’s documents.

复合索引结构示意图如下所示:

「生产事故」MongoDB复合索引引发的灾难

该索引刚好和我们讨论的是一样的,userid顺序,score倒序。

我们需要直面第二个问题:复合索引在使用时需不需要在乎方向?

假设两个查询条件:

  1. # 查询 一 
  2. db.getCollection("records").find({  
  3.   "userid" : "ca2" 
  4. }).sort({"score" : -1.0}); 
  5.  
  6.  
  7. # 查询 二 
  8. db.getCollection("records").find({  
  9.   "userid" : "ca2" 
  10. }).sort({"score" : 1.0}); 

上述的查询没有任何问题,因为受到score字段排序的影响,只是数据从左侧还是从右侧遍历的问题,那么下面的一个查询呢?

  1. # 错误示范 
  2. db.getCollection("records").find({  
  3.   "userid" : "ca2"
  4.   "score" : {  
  5.     "$lt" : NumberLong(2038092587) 
  6.   } 
  7. }).sort({"score" : -1.0}); 

错误原因如下:

  • 由于score字段按照倒序排序,因此为了使用该索引,所以需要从左侧开始遍历
  • 从倒序顺序中找小于某个值的数据,势必会扫描很多无用数据,然后丢弃,当前场景下找大于某个值才是最佳方案
  • 所以MongoDB为了更多场景考虑,在该种情况下,放弃了复合索引,选用其他的索引,如 score 的单列索引

针对性修改

仔细阅读了根源之后,再回顾线上的查询语句,如下:

  1. ### 原始Query 
  2. db.getCollection("sku_main").find({  
  3.         "orgCode" : NumberLong(337451),  
  4.         "fixedStatus" : {  
  5.             "$in" : [ 
  6.                 1.0,  
  7.                 2.0 
  8.             ] 
  9.         },  
  10.         "shopCategories" : {  
  11.             "$exists" : false 
  12.         },  
  13.         "_id" : {  
  14.             "$lt" : NumberLong(2038092587) 
  15.         } 
  16.     } 
  17. ).sort( 
  18.     {  
  19.         "_id" : -1.0 
  20.     } 
  21. ).skip(1000).limit(1000); 
  22.  
  23. ### 期望的索引 
  24. db.sku_main.ensureIndex({"orgCode": 1, "_id": -1},{background:true}); 

犯的错误一模一样,所以MongoDB放弃了复合索引的使用,该为单列索引,因此进行针对性修改,把 $lt 条件改为 $gt 观察优化结果:

  1. # 原始查询 
  2. [TEMP INDEX] => lt: {"limit":1000,"queryObject":{"_id":{"$lt":2039180008},"categoryId":23372,"orgCode":351414,"fixedStatus":{"$in":[1,2]}},"restrictedTypes":[],"skip":0,"sortObject":{"_id":-1}} 
  3.  
  4. # 原始耗时 
  5. [TEMP LT] => 超时 (超时时间10s) 
  6.  
  7. # 优化后查询 
  8. [TEMP INDEX] => gt: {"limit":1000,"queryObject":{"_id":{"$gt":2039180008},"categoryId":23372,"orgCode":351414,"fixedStatus":{"$in":[1,2]}},"restrictedTypes":[],"skip":0,"sortObject":{"_id":-1}} 
  9.  
  10. # 优化后耗时 
  11. [TEMP GT] => 耗时: 383ms , List Size: 999 

总结

分析了小2000字,其实改动就是两个字符而已,当然真正的改动需要考虑业务的需要,但是问题既然已经定位,修改什么的就不难了,回顾上述内容总结如下:

  • 学习数据库知识的时候可以用类比的方式,但是需要额外注意其不同的地方(MySQL、MongoDB索引、索引的方向)
  • MongoDB数据库单列索引可以不在乎方向,如对无索引字段排序需要控制数据量级(32M)
  • MongoDB数据库复合索引在使用中一定要注意其方向,要完全理解其逻辑,避免索引失效

本文转载自微信公众号「是Kerwin啊」,可以通过以下二维码关注。转载本文请联系是Kerwin啊公众号。

「生产事故」MongoDB复合索引引发的灾难

 

延伸 · 阅读

精彩推荐
  • MongoDB在mac系统下安装与配置mongoDB数据库

    在mac系统下安装与配置mongoDB数据库

    这篇文章主要介绍了在mac系统下安装与配置mongoDB数据库的操作步骤,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪...

    CXYhh1219312021-11-14
  • MongoDBMongoDB多条件模糊查询示例代码

    MongoDB多条件模糊查询示例代码

    这篇文章主要给大家介绍了关于MongoDB多条件模糊查询的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用MongoDB具有一定的参考学习价值...

    浅夏晴空5902020-05-25
  • MongoDBMongoDB的索引

    MongoDB的索引

    数据库中的索引就是用来提高查询操作的性能,但是会影响插入、更新和删除的效率,因为数据库不仅要执行这些操作,还要负责索引的更新 ...

    MongoDB教程网2532020-05-12
  • MongoDBmongodb数据库基础知识之连表查询

    mongodb数据库基础知识之连表查询

    这篇文章主要给大家介绍了关于mongodb数据库基础知识之连表查询的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用mongodb具有一定的参...

    ZJW02155642020-05-22
  • MongoDBMongoDB查询之高级操作详解(多条件查询、正则匹配查询等)

    MongoDB查询之高级操作详解(多条件查询、正则匹配查询等)

    这篇文章主要给大家介绍了关于MongoDB查询之高级操作(多条件查询、正则匹配查询等)的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者...

    w田翔3872020-12-19
  • MongoDBWindows下MongoDB配置用户权限实例

    Windows下MongoDB配置用户权限实例

    这篇文章主要介绍了Windows下MongoDB配置用户权限实例,本文实现需要输入用户名、密码才可以访问MongoDB数据库,需要的朋友可以参考下 ...

    MongoDB教程网3082020-04-29
  • MongoDBMongoDB系列教程(五):mongo语法和mysql语法对比学习

    MongoDB系列教程(五):mongo语法和mysql语法对比学习

    这篇文章主要介绍了MongoDB系列教程(五):mongo语法和mysql语法对比学习,本文对熟悉Mysql数据库的同学来说帮助很大,用对比的方式可以快速学习到MongoDB的命...

    MongoDB教程网3252020-05-01
  • MongoDBMongodb索引的优化

    Mongodb索引的优化

    MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。接下来通过本文给大家介绍Mongodb索引的优化,本文介绍的非常详细,具有参考借鉴价值,感...

    MRR3252020-05-05