对比mongodb查询的执行计划,说一说组合索引的优化方案(上)

一、背景

Mongodb数据库,有个160w数据量规模的集合,字段多达几十个,随着需求的迭代,查询条件也是五花八门。
为了提高某个查询的效率,结果都以新增索引解决问题,最后多达16个索引。
在这里插入图片描述
这里仅贴出本文会提及的索引,可以看到,都是组合索引。

且不说索引越多,更新记录的时候会越耗时,查询想要匹配索引的时候却找不到想要的索引。

主要的害处是,一个查询语句不知道将会匹配哪个索引,既有单个索引,又有许多类似的组合索引。

本文主要是由一个慢查询语句抛出问题,先分析执行计划,然后给出优化建议。

二、定义mongodb集合和索引

字段名类型描述示例
createdOnDate创建时间ISODate(“2024-04-28T00:00:00.000Z”)
classroomNameString名称英语期末考前复习课程
creatorNameString创建者名称张三
totalIdsList人员集合[10001,10002,10003]
isDeleteBoolean是否逻辑删除true/false
recycleint回收标记0-未回收,1-已回收
authint公开标记0-否,1-是

前后定义了16个索引,这里列出本文会涉及到的三个索引。

  • createdOn_1_auth_1_isDelete_1_recycle_1_creatorName_1
  • totalIds_1_isDelete_1_recycle_1_creatorName_1
  • totalIds_1_createdOn_2

三、慢查询分析

从阿里云Mongodb的慢日志,找到是哪个慢语句。
在这里插入图片描述

找到command命令,见下:

{"$and":[{"$and":[{"$and":[{"isDelete":false},
{"$or":[{"auth":1},{"totalIds":{"$in":[10001]}}]},
{"$or":[{"recycle":null},{"recycle":0}]}],
"classroomName":{"$regex":".*大口加小口.*","$options":""}},
{"createdOn":{"$gte":{"$date":"2023-04-29T00:00:00.000+0800"}}}]},
{"createdOn":{"$lte":{"$date":"2024-04-30T00:00:00.000+0800"}}}]},

"projection":{"$sortKey":{"$meta":"sortKey"}},
"sort":{"createdOn":-1},
"limit":{"$numberLong":"20"}

解析下这个查询语句的业务需求:

用户10001查询近一年的公开课或自己的课程,查询条件是课程名称正则匹配。(另外必须是未删除且未被放入回收站的课程)

1、真实的查询语句

db.{集合名}.find(
  {
    "$and": [
      {
        "$and": [
          { "isDelete": false },
          {
            "$or": [
              { "auth": 1 },
              { "totalIds": { "$in": [10001] } }
            ]
          },
          {
            "$or": [
              { "recycle": null },
              { "recycle": 0 }
            ]
          },
          {
            "classroomName": { "$regex": ".*大口加小口.*", "$options": "" }
          },
          {
            "createdOn": {
              "$gte": ISODate("2023-04-29T00:00:00.000Z")
            }
          }
        ]
      },
      {
        "createdOn": {
          "$lte": ISODate("2024-04-30T00:00:00.000Z")
        }
      }
    ]
  },
  {
    "$sortKey": { "$meta": "sortKey" }
  }
).sort({ "createdOn": -1 }).limit(20);

2、执行计划

db.{集合名}.explain().find{…} 得到上面语句的执行计划。

在这里插入图片描述- parsedQuery :被解析的查询语句(不包括分页和排序)

  • winningPlan:选择的执行计划
  • rejectedPlans:被拒的执行计划
  • executionStats:执行分析(重点)

下面重点看executionStats部分:

由内到外的步骤分别是: IXSCAN -> FETCH -> SORT_KEY_GENERATOR -> PROJECTION -> LIMIT -> SINGLE_SHARD。

'executionStats': {
     'nReturned': 20,
     'executionTimeMillis': 3222,
     'totalKeysExamined': 132375,
     'totalDocsExamined': 131245,
     'executionStages': {
         'stage': "SINGLE_SHARD",
         'nReturned': 20,
         'executionTimeMillis': 3222,
         'totalKeysExamined': 132375,
         'totalDocsExamined': 131245,
         'totalChildMillis': NumberLong("3219"),
         'shards': [
             {
                 'shardName': "d-bp1cef3c8241a8a4",
                 'executionSuccess': true,
                 'executionStages': {
                     'stage': "LIMIT",
                     'nReturned': 20,
                     'executionTimeMillisEstimate': 684,
                     'works': 132377,
                     'advanced': 20,
                     'needTime': 132356,
                     'needYield': 0,
                     'saveState': 6205,
                     'restoreState': 6205,
                     'isEOF': 1,
                     'invalidates': 0,
                     'limitAmount': 20,
                     'inputStage': {
                         'stage': "PROJECTION",
                         'nReturned': 20,
                         'executionTimeMillisEstimate': 678,
                         'works': 132376,
                         'advanced': 20,
                         'needTime': 132356,
                         'needYield': 0,
                         'saveState': 6205,
                         'restoreState': 6205,
                         'isEOF': 0,
                         'invalidates': 0,
                         'transformBy': {
                             '$sortKey': {
                                 '$meta': "sortKey"
                             }
                         },
                         'inputStage': {
                             'stage': "SORT_KEY_GENERATOR",
                             'nReturned': 20,
                             'executionTimeMillisEstimate': 675,
                             // 略
                             'inputStage': {
                                 'stage': "FETCH",
                                 'filter': {
                                     // 略
                                 },
                                 'nReturned': 20,
                                 'executionTimeMillisEstimate': 672,
                                 // 略
                                 'inputStage': {
                                     'stage': "IXSCAN",
                                     'nReturned': 131245,
                                     'executionTimeMillisEstimate': 89,
                                     // 略
                                 }
                             }
                         }
                     }
                 }
             }
         ]
     }
 }

下面从内到外看其步骤:

  • IXSCAN

IXSCAN/索引扫描,因为createdOn匹配到索引createdOn_1_auth_1_isDelete_1_recycle_1_creatorName_1,返回记录数为132375。同时,也可以看到它的耗时在几个阶段中,还算是比较快的–89毫秒。

'stage': "IXSCAN",
'nReturned': 131245,
'executionTimeMillisEstimate': 89,
'works': 132375,
'advanced': 131245,
'needTime': 1130,
'needYield': 0,
'saveState': 6205,
'restoreState': 6205,
'isEOF': 0,
'invalidates': 0,
'keyPattern': {
    'createdOn': 1.0,
    'auth': 1.0,
    'isDelete': 1.0,
    'recycle': 1.0,
    'creatorName': 1.0
},
'indexName': "createdOn_1_auth_1_isDelete_1_recycle_1_creatorName_1",
'isMultiKey': false,
'multiKeyPaths': {
    'createdOn': [
    ],
    'auth': [
    ],
    'isDelete': [
    ],
    'recycle': [
    ],
    'creatorName': [
    ]
},
'isUnique': false,
'isSparse': false,
'isPartial': false,
'indexVersion': 2,
'direction': "backward", // 倒序查询
'indexBounds': {
    'createdOn': [
        "[new Date(1714348800000), new Date(1682640000000)]"
    ],
    'auth': [
        "[MaxKey, MinKey]"
    ],
    'isDelete': [
        "[false, false]"
    ],
    'recycle': [
        "[MaxKey, MinKey]"
    ],
    'creatorName': [
        "[MaxKey, MinKey]"
    ]
},
'keysExamined': 132375,
'seeks': 1131,
'dupsTested': 0,
'dupsDropped': 0,
'seenInvalidated': 0
  • FETCH

根据索引检索指定的文档,上一步是对查询条件createdOn、isDelete和recycle进行检索。FETCH则是基于索引数据进行再次检索,条件是两个or查询条件和classroomName模糊查找。

  'stage': "FETCH",
  'filter': {
      '$and': [
          {
              '$or': [
                  {
                      'auth': {
                          '$eq': 1
                      }
                  },
                  {
                      'totalIds': {
                          '$eq': 10001
                      }
                  }
              ]
          },
          {
              '$or': [
                  {
                      'recycle': {
                          '$eq': null
                      }
                  },
                  {
                      'recycle': {
                          '$eq': 0
                      }
                  }
              ]
          },
          {
              'classroomName': {
                  '$regex': ".*大口加小口.*"
              }
          }
      ]
  },
  'nReturned': 20,
  'executionTimeMillisEstimate': 672,
  'works': 132375,
  'advanced': 20,
  'needTime': 132355,
  'needYield': 0,
  'saveState': 6205,
  'restoreState': 6205,
  'isEOF': 0,
  'invalidates': 0,
  'docsExamined': 131245,
  'alreadyHasObj': 0,
  'inputStage': {
      'stage': "IXSCAN",
      'nReturned': 131245,
      'executionTimeMillisEstimate': 89,
      // 略
  }
  • SORT_KEY_GENERATOR

返回20条数据,耗时675毫秒,所以说排序很耗时,从这里也可以看到了。它对13万多条数据进行排序,最后得到想要的20条记录,花费的时间堪比上一步FETCH了。

'stage': "SORT_KEY_GENERATOR",
'nReturned': 20,
'executionTimeMillisEstimate': 675,
'works': 132376,
'advanced': 20,
'needTime': 132356,
'needYield': 0,
'saveState': 6205,
'restoreState': 6205,
'isEOF': 0,
'invalidates': 0,
'inputStage': {
    'stage': "FETCH",
    'filter': {
        // 略
    },
    'nReturned': 20,
    'executionTimeMillisEstimate': 672,
    // 略
    'inputStage': {
        'stage': "IXSCAN",
        'nReturned': 131245,
        'executionTimeMillisEstimate': 89,
        // 略
    }
}
}

其他stage就不继续往后展开了,这里总结一下,这个查询语句虽然匹配了索引,但是因为是近一年的时间区间,匹配到的索引,但数据量也很大,导致最后查询慢。

是什么原因导致没有匹配另外的索引 totalIds_1_isDelete_1_recycle_1_creatorName_1 呢?
我们后面专门为此建立的组合索引 totalIds_1_createdOn_2 也是没有被匹配上。

这主要是因为totalIds查询条件是在or里面,见下:
在这里插入图片描述

如果我们想要对totalIds使用上索引,这也提醒我们在平常编程的时候,不能放在or查询条件里。

下一篇文章,我们将对查询条件进行调整,试着匹配包含totalIds字段的组合索引。

四、总结

本文对线上出现的一个mongodb慢查询,分析了其执行计划,也让我们知道了,要提高查询效率,不仅要求匹配索引,还要考虑排序可能带来的性能消耗。

但是,按创建时间进行排序展示数据,作为普遍的一个业务需求,我们不能在jvm内存里排序,所以不仅要对创建时间建立组合索引,更要把它放在组合索引的末尾。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/586068.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

引领农业新质生产力,鸿道(Intewell®)操作系统助力农业机器人创新发展

4月27日至29日,2024耒耜国际会议在江苏大学召开。科东软件作为特邀嘉宾出席此次盛会,并为江苏大学-科东软件“农业机器人操作系统”联合实验室揭牌。 校企联合实验室揭牌 在开幕式上,江苏大学、科东软件、上交碳中和动力研究院、遨博智能研究…

Spring Boot Admin

概述 Spirng Boot Admin 登录页面 Spring Boot Admin是一个用于管理Spring Boot应用的监控工具,它允许你查看和管理多个Spring Boot应用实例。用于应用信息进行界面化的展示,常常辅助我们开发人员快速查看服务运行状态在微服务架构中,Spring Boot Admin通…

中科院突破:TalkingGaussian技术实现3D人脸动态无失真,高效同步嘴唇运动!

DeepVisionary 每日深度学习前沿科技推送&顶会论文分享,与你一起了解前沿深度学习信息! 引言:探索高质量3D对话头像的新方法 在数字媒体和虚拟互动领域,高质量的3D对话头像技术正变得日益重要。这种技术能够在虚拟现实、电影…

谷粒商城实战(020 RabbitMQ-消息确认)

Java项目《谷粒商城》架构师级Java项目实战,对标阿里P6-P7,全网最强 总时长 104:45:00 共408P 此文章包含第258p-第p261的内容 消息确认 生产者 publishers 消费者 consumers 设置配置类 调用api 控制台 抵达brocker 代理 新版本ReturnCallbac…

【webrtc】MessageHandler 8: 基于线程的消息处理:处理音频输入输出断开

m98代码,看起来m114 去掉了MessageHandler :音频的录制和播放 都使用了on message,但只是用来通知并处理流的断开的。AAudioRecorder AAudioRecorder 处理流断开 OnErrorCallback :有可能 错误回调是别处来的,是其他线程, 但是这个错误的处理要再自己的线程执行: 音频播…

北京大学肖臻老师《区块链技术与应用》P14(ETH概述)和P15(ETH账户)

1️⃣ 参考 北京大学肖臻老师《区块链技术与应用》 P14 - ETH概述篇P15 - ETH账户篇 1️⃣4️⃣ETH概述 ① 比特币与以太坊的对比 比特币(区块链 1.0)以太坊(区块链 2.0)出块时间大约10 min十几秒mining puzzle计算密集型Memo…

【计算智能】基本遗传算法在优化问题中的应用与实验【理论到程序】

文章目录 1. 引言:遗传算法简介2. 基本遗传算法(SGA)2.1 基本遗传算法的构成要素1. 染色体编码2. 适应度函数3. 遗传算子 2.2 实验设计与方法1. 算法流程2. 伪代码3. python实现1. 导入模块2. 目标函数 f(x)3 初始化种群4. 计算适应度5. 选择…

Django后台项目开发实战二

我们的需求是开发职位管理系统 三个功能: 管理员发布职位候选人能浏览职位用户能投递职位 第二阶段 创建应用 jobs,实现职位数据的建模 python manage.py startapp jobs 然后再 setting .py 注册应用,只需添加应用名称到最后一行 INST…

VTK —— 二、教程六 - 为模型加入3D微件(按下i键隐藏或显示)(附完整源码)

代码效果 本代码编译运行均在如下链接文章生成的库执行成功,若无VTK库则请先参考如下链接编译vtk源码: VTK —— 一、Windows10下编译VTK源码,并用Vs2017代码测试(附编译流程、附编译好的库、vtk测试源码) 教程描述 本…

探索未来道路:智慧高速系统架构的革命性进步

随着科技的飞速发展,智慧高速系统架构正在成为道路交通领域的一项重要创新。这一系统结合了先进的信息技术和智能化设备,为高速公路提供了全新的管理和服务模式,极大地提升了交通运输效率和安全性。本文将深入探讨智慧高速系统架构的革命性进…

ping命令操作记录

1,ping 主机可查看主机是否在线 2,ping -a参数,解析主机的名称 3,ping -r 跟踪打印路由信息 ping命令的作用:确认目标主机是否存活,确定网络是否畅通 ping的原理:ping发送ICMP(互联…

【算法】【贪心算法】【leetcode】870. 优势洗牌

题目地址:https://leetcode.cn/problems/advantage-shuffle/description/ 题目描述: 给定两个长度相等的数组 nums1 和 nums2,nums1 相对于 nums2 的优势可以用满足 nums1[i] > nums2[i] 的索引 i 的数目来描述。 返回 nums1 的任意排列&…

Open CASCADE学习|BRepFill_SectionPlacement

BRepFill_SectionPlacement 是一个与计算机辅助设计(CAD)相关的术语,通常用于指代一个几何对象或操作,它是Open CASCADE Technology(OCCT)中的一个类。Open CASCADE Technology是一个开源的CAD内核&#xf…

AnomalyGPT——使用大型视觉语言模型进行工业异常检测的算法解析与应用

1.概述 工业缺陷检测是工业自动化和质量控制中的一个重要环节,其目的是在生产过程中识别和分类产品或组件中的缺陷,以确保最终产品的质量满足既定标准。这项技术的应用可以显著提高生产效率,降低成本,并减少由于缺陷产品导致的潜…

数据挖掘之基于K近邻算法的原油和纳斯达克股票数据预测分析

欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 在当今日益复杂的金融市场中,准确地预测原油价格和纳斯达克股票市场的走势对于投资者、政…

【docker 】Windows10安装 Docker

安装 Hyper-V Hyper-V 是微软开发的虚拟机,仅适用于 Windows 10。 按键: win键X ,选着程序和功能 在查找设置中输入:启用或关闭Windows功能 选中Hyper-V 点击确定 安装 Docker Desktop for Windows Docker Desktop 官方下载…

【漏洞复现】zookeeper AdminServer 未授权访问漏洞

0x01 产品简介 ZooKeeper 是一个集中式服务,用于维护配置信息、命名、提供分布式同步和提供组服务。ZooKeeper的AdminServer是其管理界面的一部分,通常用于监控ZooKeeper集群的状态和执行一些管理操作。AdminServer提供了Web-based的管理和监控功能&…

Springboot+Vue项目-基于Java+MySQL的入校申报审批系统(附源码+演示视频+LW)

大家好!我是程序猿老A,感谢您阅读本文,欢迎一键三连哦。 💞当前专栏:Java毕业设计 精彩专栏推荐👇🏻👇🏻👇🏻 🎀 Python毕业设计 &…

从MySQL+MyCAT架构升级为分布式数据库,百丽应用OceanBase 4.2的感受分享

本文来自OceanBase的客户,百丽时尚的使用和测试分享 业务背景 百丽时尚集团,作为国内大型时尚鞋服集团,在中国超过300个城市设有直营门店,数量超过9,000家。集团构建了以消费者需求为核心的垂直一体化业务模式,涵盖了…

Nginx实现端口转发与负载均衡配置

前言:当我们的软件体系结构较为庞大的时候,访问量往往是巨大的,所以我们这里可以使用nginx的均衡负载 一、配置nginx实现端口转发 本地tomcat服务端口为8082 本地nginx端口为8080 目的:将nginx的8080转发到tomcat的8082端口上…