一个数组查询引发的坑

guwj
发布于 2022-11-24 11:47
浏览
0收藏

背景

中午12点半,接到了线上MongoDB 数据库异常的告警通报:

“CPU不间断飙升到百分百,业务也相应出现了抖动现象。”


通过排查数据库主节点的日志,发现了这样的一个慢语句:


一个数组查询引发的坑-鸿蒙开发者社区


从语句中初步判断,“keysExamined”和docsExamined 显示扫描了100W 条记录,其中也用到了下面的索引:


一个数组查询引发的坑-鸿蒙开发者社区


跟研发兄弟确认过后,该查询的目的是 找到某些应用下带指定标签的设备信息,按ID分段去获取,每次只查询10条。

关于索引的设计也已经确认过是最优的了,而且此前在开发环境中一直没有出现过问题,不知道为什么这次就出问题了。


详细分析

接下来,看了下该集合的模型,大致是长下面的样子:


一个数组查询引发的坑-鸿蒙开发者社区

说明

除了其他的属性之外,tags字段采用了嵌套文档数组的结构;
每一个元素都对应了一个tag对象,包含 tagName/tagValue/tagType几个字段。


然后是查询的模式


一个数组查询引发的坑-鸿蒙开发者社区


这从索引的前缀匹配来看,是应该没有问题的,索引的定义如下所示:


一个数组查询引发的坑-鸿蒙开发者社区


为了避免对线上环境造成影响,我们找了一个测试环境来做了尝试复现,执行:


一个数组查询引发的坑-鸿蒙开发者社区


结果却跟线上的情况不大一样,这次选中的是_id索引!


一个数组查询引发的坑-鸿蒙开发者社区


而同样的是也扫描了100W+的记录数,于是大家认为可能索引的选择器出了问题,但就算是选择器的问题也仍然没办法解释线上出现的现象(线上的索引可是命中的)

为了一探究竟,我们使用 hint 强制让查询命中 appId_1_tags.tagName_1_tags.tagValue_1__id_1这个索引:


一个数组查询引发的坑-鸿蒙开发者社区


这一次的结果显示确实命中了对应的索引:

一个数组查询引发的坑-鸿蒙开发者社区


然而,在整个执行过程中(executionStats),出现了内存排序(SORT)。

而且,从一开始命中** appId_1_tags.tagName_1_tags.tagValue_1__id_1 **这个索引的阶段中,就已经扫描了100W条记录,简直不可思议!


但同时,我们也从indexBounds的指示中找到了端倪:

appId、tags.tagName 都命中了单值,在 tags.tagValue 的路径节点上却覆盖了全部范围!

由于中间索引节点出现了大范围覆盖,导致最终需要在内存中对大量的数据做 _id字段的排序,这个就是导致慢操作的原因!

解决问题

既然从前面的分析中找到了问题的来源,我们的推论如下:


既然索引的命中没有问题,那么导致大范围扫描的只可能是查询模式的问题。


再次拿出前面的查询条件:


一个数组查询引发的坑-鸿蒙开发者社区


在索引的匹配中,只能单键命中tags.tagName: “pipeline” 这一个条件,那么由于 tags是一个嵌套文档的数组,
对于上面的查询,语义上是指那些 包含某个元素 可命中tagName,且包含某个元素 可命中 tagValue的文档,这里面并不要求 同一个元素同时命中tagName和tagValue。


但 MongoDB 在嵌套数组索引的构建上是按照同一个元素的字段组合去构建的。 关于这点,可以参考下面的地址:

​https://docs.mongodb.com/manual/core/index-multikey/#multikey-embedded-documents​


对于数组元素的条件匹配,应该使用 $elemMatch,其用法可​​参考这里​


为此,我们构建了下面的查询:


一个数组查询引发的坑-鸿蒙开发者社区


执行后输出如下:


一个数组查询引发的坑-鸿蒙开发者社区


这个结果是令人满意的,除了自动命中合适的索引之外,这个查询过程也达到了最优的路径匹配,扫描记录数才10条!

最后,根据该方案调整了查询模式,线上的问题得到恢复。

小结

看似很简单的一个查询语句,没想到会出现这么大的坑,其实无论是作为开发人员还是DBA,都应当谨慎对待你的SQL。

重要的事情说三遍!!! SQl查询上线前务必 explain、务必分析到位,这难道没有道理?



文章转载自公众号: Mongoing中文社区

分类
标签
已于2022-11-24 11:47:30修改
收藏
回复
举报
回复
    相关推荐