MongoDB 4.x——合理使用索引(二)

发布时间:2026/11/5 11:28:57

MongoDB 4.x——合理使用索引(二) 合理使用索引6、查询缓存原理6.1、工作流程6.2、案例6.3、内部原理6.3.1、查询优化器如何选择最优计划6.3.2、如何保证已缓存计划的效率6.3.3、如何清理计划缓存7、强制命中7.1、使用hint方法7.2、使用IndexFilter方法8、索引正交8.1、索引正交8.2、一些限制9、使用MongoDB Compass10、优化原则10.1、在适当的时机使用索引10.2、使用前缀匹配10.3、避免低效的操作符10.4、使用覆盖索引优化10.5、高基数优先原则10.6、控制索引的数量10.7、避免设计过长的数组索引10.8、避免创建重复的索引10.9、删除无用的索引10.10、避免深度分页10.11、避免一次性返回大量结果集10.12、谨防内存排序10.13、避免大量的扫描10.14、为索引预留足够的内存6、查询缓存原理MongoDB通过查询计划query plan来描述一个查询语句的执行过程。通常情况下一个查询操作可能对应多个不同的查询计划例如对于{x100y100}这个条件既可以选择{x1}索引也可以选择{y1}索引甚至是全表扫描计划。但无论是哪一种方式都会先经过内部的评分机制进行评估最终选出一个最优的执行方案。那么查询计划的评估必然会产生一定的计算开销。如果不进行缓存数据库就无法应对高吞吐、高性能的场景。为此MongoDB提供了PlanCache用以实现查询计划的缓存能力可以避免在一定条件内对同一个查询模型进行重复性的分析、评估工作。而且如果对于某个查询已经有了确定性的选择查询优化器会直接做出选择此时缓存并不会启用。例如对于已经存在{a1}索引的集合在执行find{a100}这样的查询时很可能并不会产生查询计划缓存。6.1、工作流程查询计划缓存的具体工作流程如图所示。说明查询开始执行判断PlanCache中是否有对应的缓存。如果没有缓存则进入计划生成阶段执行下列步骤。分析查询语句与全部索引产生候选计划。评估查询计划包括对计划的并发执行、采样。选择最优的计划。将计划存入缓存。如果存在缓存则触发replanning机制评估查询性能此时有以下两种结果。查询性能不达标淘汰缓存重新生成计划。查询性能达标采纳计划。执行最终计划返回结果。查询模型查询模型query shape是对于当前查询场景的唯一性结构描述。查询优化器会首先将查询请求解析为某个查询模型根据这个模型再进行计划缓存的查询。查询模型的组成包括以下3个部分。query描述查询条件的结构该结构由条件的字段、操作符谓词以及条件的嵌套关系组成并不包含查询条件的具体值。projection描述即将返回哪些字段。sort描述排序的规则。6.2、案例为了呈现查询计划缓存我们为集合创建了两个索引db.foo.createIndex({x:1})db.foo.createIndex({y:1})接着执行如下的查询db.foo.find({x:{$lt:10},y:{$gt:54}},{x:1,z:1}).sort({z:-1})不难判断无论是使用索引{x1}还是{y1}都无法完美地匹配这次查询。因此MongoDB会为当前的查询评选出一个最优的计划并写入PlanCache。记住没有确定性选择是产生计划缓存的关键。对于update操作中的查询同样可能触发查询计划的评估MongoDB对此会一视同仁。如果你执行的是explain命令那么缓存则一定不会被使用。使用PlanQuery的listQueryShapes方法可以展示当前已经缓存的查询模型代码如下db.foo.getPlanCache().list()返回的结果是一个数组包含了刚刚的查询模型而且query、sort、projection这几个字段也正如我们预期的一样。queryHash是由查询模型计算得出的稳定哈希值该字段也会在explain的结果中出现。需要注意的是尽管query字段呈现了查询条件中具体的值但在计算模型结构时这些条件值都会被忽略。也就是说query{x{$lt10}y{$gt55}}和query{x{$lt100}y{$gt0}}在结构上仍然是相同的最终的queryHash值也是一致的。使用PlanQuery的getPlansByQuery方法可以查看针对某个查询模型所产生的计划缓存代码如下返回结果如下上述结果中的plans为一个数组其中包含了当前查询可能采取的多个计划。reason.score是指得分得分越高的计划被采纳的概率越大reason.stats则是对这个计划进行评估时的一些执行过程信息和explain命令的输出基本一致。6.3、内部原理6.3.1、查询优化器如何选择最优计划最初查询优化器需要根据现有的上下文产生一些候选计划。如果同时存在多个候选计划那么需要根据一种评分机制从这些计划中选出一个最优计划这就涉及计划的评优evaluate过程如图所示。具体的机制如下。首先让所有计划都同时执行一定量的扫描任务扫描任务在满足以下条件时停止扫描次数达到numWorks次numWorksMath.max100000.3×collection.count​。返回结果达到numResults个numResultsMath.min101query.getN()query.getLimit()​其中query.getN()来自getMore命令query.getLimit()则只有限制了limit条件才会出现。这两个参数只有存在时才会参与比较否则numResults默认就是101。然后为每个计划的执行情况打分计算分数的因子来自下面几点isEOF是否为true如果出现isEOE则说明扫描的指针已经到达了末尾。如果计划提前结束了则扫描会获得最大的机会。advance/workUnits%​如果返回的结果数占扫描数的比例越大则代表扫描效率越高。是否存在以下低效率的阶段PROJECTIONFETCH非覆盖索引查询​、SORT内存排序​、AND_HASH|STAGE_AND_SORTED索引正交阶段​。任意一种低效阶段的存在都会导致候选计划被扣分。最后根据所得分数进行排序得分最高的计划被评选为最优计划并写入缓存。6.3.2、如何保证已缓存计划的效率事实上对于已经缓存的计划MongoDB仍会采用一种replaining的机制来保证其高效性。在返回缓存的计划之前首先对该计划进行扫描采样这次的采样数相比之前的numWorks会扩大10倍。扫描过程中如果返回了numResults个条目或者到达了EOF则达到通过pass条件此时仍然选用此计划。如果超过采样数之后仍未达到通过条件则转为失败fail状态触发replain过程此时将重新评选计划。如果扫描过程中出错则同样会变成失败状态此时也会触发replain过程。6.3.3、如何清理计划缓存MongoDB在发生如下一些情况时可实现计划缓存的清除。执行了PlanCache.clear方法。创建、删除索引或者执行了集合的drop操作。将MongoDB进程重启。查询缓存功能属于MongoDB的内部机制官方文档并未对此提供特别详细且公开的描述。7、强制命中7.1、使用hint方法在某些极少情况下某些查询产生的最终计划可能并不是你想要的。事实上想实现一个完美的查询计划是非常困难的MongoDB在查询优化器上做了很多合理性方面的努力也提供了一些干预的手段。hint方法实现了对查询计划机制的干预在查询计划中使用hint语句可以让MongoDB忽略查询优化器的结果从而直接使用指定的索引。比如下面的做法db.test.find().hint({name:}).explain()该语句将被强制使用{name1}这个索引。如果没有指定则会使用全表扫描的方式。hint方法的参数可以传入索引的定义对象也可以是索引的名称代码如下db.test.find().hint(name_1)如果所传入的索引并不存在则将会得到错误提示。7.2、使用IndexFilter方法IndexFilter方法也可以于预查询索引代码如下db.runCommand({planCacheSetFilter:test,query:{a:3,b:4},indexes:[{b:1}]})执行planCacheSetFilter命令会在test集合中增加一个IndexFilter对象该对象将会自动关联到同时包含a字段与b字段的等值查询并引导查询优化器使用{b1}这个索引。执行planCacheListFilters命令可以查看它的定义代码如下db.runCommand({planCacheListFilters:test}){filters:[{query:{a:3,b:4},sort:{},projection:{},indexes:[{b:1}]}],ok:1}其中查询模型query shape会忽略查询条件中具体的数值因此下面的查询是适用的db.test.find({a:99,b:-1})db.test.find({a:3,b:10})对于关联了IndexFilter的查询在explain结果中的indexFilterSet字段为true代码如下在上述例子中如果使用了非等值查询如ge、lt​或者排序sort​、投射projection发生了变化就会导致匹配失效。IndexFilter的优先级高于hint方法如果查询优化器发现了关联的IndexFilter则一定会忽略hint语句。但是IndexFilter并不能保证查询优化器最终一定会选择对应的索引事实上优化器会将这些索引与全表扫描方式一并进行评估再抉择出最终的结果。在最坏的情况下如果我们指定了不存在的索引就会导致全表扫描。IndexFilter是内存态的如果重启了MongoDB则会自动失效。另外也可以使用planCacheClearFilters进行擦除代码如下db.runCommand({planCacheClearFilters:test})注意MongoDB的查询优化器已经足够强大无论是hint还是IndexFilter都会改变默认的查询优化行为。除非迫不得已否则应该尽量少用。8、索引正交8.1、索引正交在符合“前缀匹配”的原则下组合索引可以满足一些不同的查询模式。对于如下的索引{x:1,y:1}可以很好地满足以下查询db.test.find({x:100})db.test.find({x:100,y:9})db.test.find({x:{$gt:10},y:{$lt:3}})但是对于以下的查询却无能为力db.test.find({y:5})db.test.find().sort({y:1})索引正交是一种特殊的检索方式它会利用多个索引来满足当前的查询。如果将上面的组合索引拆为如下两个{x:1}{y:1}此时利用索引正交的特点就可以同时满足上述这些查询。另一个问题是如果查询中存在排序会是什么情形呢假定有以下索引{x:1,y:1}{z:1}{y:1}对于下面的查询仍然不适用db.test.find({z:3}).sort({y:1})但是如果稍微做一下调整就可以使用索引正交db.test.find({z:3,x:10}).sort({y:1})这里的关键点在于需要先存在一个索引能同时覆盖查询和排序字段上面的这个查询会先以{x1y1}为基础结合索引{z1}进行正交计算。如果查询使用了索引正交则其explain结果会表示为AND_SORT 或者AND_HASH类型。8.2、一些限制在索引正交的实现上MongoDB规定最多只能使用两个索引。在大多数情况下查询优化器很可能并不会选择索引正交作为最优计划。这其中的原因来自一些可变的因素。例如假设大部分的文档数据都可以通过内存获取那么查询优化器很容易会认为比起索引正交来说直接在内存中获取文档进行过滤计算的开销要小得多。9、使用MongoDB Compass对于查询分析优化除了使用explain方法还可以使用一些GUI工具。MongoDB Compass是MongoDB官方提供的GUI工具可以用于数据管理、查询优化等如图所示。MongoDB Compass分为社区版、企业版等多个不同分支不同的版本具备的功能也有些差别。总的来说MongoDB Compass主要提供了以下功能数据管理如一般的CRUD操作。表结构分析可查看各字段的取值分布需要企业版才支持。实时的服务器监控需要企业版才支持。帮助创建管道pipeline化的聚合操作。使explain结果可视化辅助查询分析优化。使用方法下载安装Compass社区版本。打开Compass选择集合并切换到Explain Plan视图。将输入框中的OPTIONS展开在FILTER处输入{x{$in​[1234]}}在SORT处输入{y1}。单击“EXPLAIN”按钮可看到详细的图形化的执行计划如图所示。10、优化原则10.1、在适当的时机使用索引记住一点索引的作用在于提高查询效率。当返回结果集只是全部文档的一小部分时增加合适的索引才能获得不错的性价比。当然你可以自己设定结果集的比例例如15%20%。10.2、使用前缀匹配对于模糊匹配的查询尽量使用前缀匹配。比如搜索名称时使用/^keyword/要明显优于/keyword/。后者很可能需要对整个索引树进行遍历。10.3、避免低效的操作符nin、ne、not不利于索引命中。相反这些操作符会导致大范围扫描建议避免这些操作。除此之外where、exist也无法利用索引优化同样需要避免。10.4、使用覆盖索引优化建议只返回需要的字段同时利用覆盖索引来提升性能。10.5、高基数优先原则基数cardinality是指某个字段拥有的唯一值的数量。以用户信息为例基数高的字段为用户身份证号、手机号等基数低的字段为性别等。一个字段的基数越高越有利于快速检索。这是因为高基数的索引能迅速将检索范围圈定到一个比较小的结果集上如果基数太小则会产生许多排除性的工作。比如想要查询“1990年10月13日出生的女性”​如果使用性别gender这个索引进行查找将只能将结果集范围缩小50%左右。一般来说唯一索引的数量是最高的基数等同于文档的数量​建议优先为基数高的字段建立索引另外组合索引上也应尽量将基数高的字段放在前面。10.6、控制索引的数量索引并不是越多越好相反过多的索引会带来一些问题多余索引会抢占内存空间导致常用的索引被挤兑影响查询性能。写操作性能降低一次文档写入会触发多个索引的更新增加了系统I/O开销。需要谨慎地对整个文档进行保存。如果使用了SpringDataMongo这样的ORM框架开发者往往会使用save命令保存整个文档此时将会触发集合中所有索引的更新。如果能在update操作中明确指定需要更新的字段那么影响则会减少一些。也就是说只有当索引中的字段被update指定时才会触发更新。不利于查询计划优化太多的索引会增加查询计划评估的难度在极端情况下可能会产生“索引跳变”​。10.7、避免设计过长的数组索引数组索引是多值的在存储时需要使用更多的空间。如果索引的数组长度特别长或者数组的增长不受控制则可能导致索引空间急剧膨胀。10.8、避免创建重复的索引组合索引具有“前缀覆盖”的特点应避免存在已经被覆盖的索引。例如db.test.createIndex({x:1,y:1,z:1})db.test.createIndex({x:1,y:1})db.test.createIndex({x:1}){x1y1z1}同时具备{x1}索引和{x1y1}索引的功能因此后面两个索引可以去掉。如果使用了哈希分片则可能会获得该分片键上的一个哈希索引。如果应用上只需要对该字段进行单个查询那么该索引已经完全可以满足。不要对分片键创建重复的索引。10.9、删除无用的索引对环境中的数据库例行检查及时删除不使用的索引。10.10、避免深度分页避免使用skip命令实现超大集合的分页该命令会消耗CPU资源当跳转到深度页面时响应会非常缓慢。10.11、避免一次性返回大量结果集对于较大的集合应避免直接使用find命令进行全表查询当返回的结果集很大时使用limit方法限制合理的返回条目数比如一次批量查询不超过1000条。这个约束还要考虑结合当前系统所能承受的性能压力一般在高并发、高实时性的场景下这个值通常要设置得更小。无论何时都应该对结果集的大小保持警惕。如果对此不加控制则很可能导致应用程序出现OOM错误。10.12、谨防内存排序对于存在排序需求的查询在缺失索引或者组合索引的排序方向不匹配的情况下会产生内存排序。内存排序需要更多的临时内存影响资源占用。此外MongoDB对于排序的内存使用有32MB的限制超过该阈值会产生失败。10.13、避免大量的扫描避免全表扫描或大范围的记录扫描通过explain结果中的totalKeysExamined、totalDocsExamined可获知当前的扫描次数。避免涉及大范围扫描的计算操作。10.14、为索引预留足够的内存为了保证性能最好保证索引能被装入内存中。当内存不足时索引需要从磁盘中加载这会导致昂贵的I/O操作。一种例外的情况是递增式的索引即索引的值呈现出递增的特点而且应用上也只关心最近产生的一部分数据。例如系统日志可以按照日期建立索引而通常我们也只关心最近几天产生的日志此时内存中则可以仅保留“被关心”的部分数据。
延伸阅读

更多相关文章

2026/11/5 11:28:56

【免费】基于Spark实时电商用户行为分析与预测 系统(Python版本+pyspark+可视化大屏+Kafka+FastAPI+Vue3) 锋哥原创出品,必属精品

大家好,我是Java1234_小锋老师,分享一套锋哥原创的基于Spark实时电商用户行为分析与预测 系统(Python版本pyspark可视化大屏KafkaFastAPIVue3) 项目介绍 随着电子商务规模持续扩大,用户在浏览、加购、收藏与购买等环节产生的行为数据呈现高…

2026/11/4 0:48:11

AI如何提升计算机教材编写效率与质量

1. 教材编写行业的痛点与AI解决方案教材编写一直是个既需要专业知识又极度耗费时间的工作。传统编写过程中,教育工作者常常面临几个核心难题:内容查重率高、专业术语表达单一、章节结构编排耗时、知识点更新滞后。这些问题直接影响了教材质量和编写效率。…

2026/11/4 8:54:02

Mamba与YOLO结合的目标检测优化实践

1. 项目背景与核心优势去年在目标检测领域出现了一个有趣的现象:当大家都在追逐Transformer架构时,Mamba这种基于状态空间模型(SSM)的新架构悄然崛起。我团队经过半年多的实验验证,发现将Mamba与YOLO结合后&#xff0c…

2026/11/5 11:24:07

Makefile自动化构建:从原理到实战技巧

1. 从零开始理解Makefile的本质 作为一个在Linux环境下摸爬滚打多年的老码农,我见过太多新手面对Makefile时露出的困惑表情。Makefile本质上是一个自动化构建脚本,但它更像是一种"构建逻辑的声明式表达"。想象你是个建筑工头,Makef…

2026/11/5 11:24:07

可跑通的ADS射频仿真实例:S参数、匹配与功放谐波平衡

简介:面向射频与微波电路设计学习者的 ADS 仿真实例合集,围绕阻抗匹配、滤波器、低噪放、功分器、功率放大器与耦合器等典型模块展开,适合希望从原理走向电路建模、仿真与优化的初中级工程师及电子类专业学生查练。压缩包为 rar 格式&#xf…

2026/11/5 11:24:07

存储过程参数过多错误分析与解决方案

1. 问题现象与背景分析 最近在排查一个生产环境数据库问题时,遇到了"CDataBaseEngineSink::OnRequsetInsertCreateRecord 数据库异常:为过程或函数 GSP_GR_InsertCreateRecord 指定了过多的参数"的错误。这个错误发生在调用存储过程GSP_GR_Ins…

2026/11/5 11:24:07

RabbitMQ消息投递可靠性保障与补偿机制实战

1. 消息队列中的投递可靠性难题 在分布式系统架构中,RabbitMQ作为经典的消息中间件,其消息投递的可靠性直接影响着业务系统的稳定性。我经历过一个电商订单系统案例:促销高峰期每秒3000订单消息通过RabbitMQ流转,有0.3%的消息神秘…

2026/11/5 11:24:06

SIL2认证的本质:从IEC 61508到ControlLogix硬件诊断的确定性实现

简介:本资源为罗克韦尔自动化(Rockwell Automation)ControlLogix系列PLC于2021年通过IEC 61508:2010标准SIL2功能安全认证的官方技术文档,面向工业自动化工程师、安全系统设计人员及PLC应用开发者,解决安全回路设计中对…

2026/11/5 11:19:06

H5游戏图集拆解实战:plist/atlas解析与Python还原脚本

简介:面向Cocos、白鹭(Egret)与Laya等2D游戏引擎开发者的图集/合图分解实用工具,用于将精灵表按plist元数据还原为单张图片,便于资源编辑、版本更新与多设备适配。压缩包共6个文件、约227KB,以可执行程序为…

2026/11/4 12:38:23

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 0:02:07

多智能体协作系统架构设计与实践优化

1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中…

2026/11/5 0:02:07

大模型系统性入门实战指南:从原理到部署微调的全链路路线

提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#…

2026/11/5 0:02:07

鸿蒙文件系统沙箱机制解析与开发实践

1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的…

2026/11/4 12:38:23

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑