索引“卡死“?3个explain技巧让你的查询快10倍 关注墨瑾轩带你探索编程的奥秘超萌技术攻略轻松晋级编程高手技术宝库已备好就等你来挖掘订阅墨瑾轩智趣学习不孤单即刻启航编程之旅更有趣MongoDB explain的复明三重奏1. explain的三重模式queryPlanner vs executionStats vs allPlansExecution在MongoDB中explain()是你的眼睛能让你看清查询的每一步。但你知道吗explain的三种模式可能正在让你的查询卡死。// 默认模式queryPlannerdb.products.explain().find({quantity:{$gt:50}})为什么说这个默认模式是陷阱只显示计划不显示执行queryPlanner模式只告诉你MongoDB选择了哪个查询计划但不告诉你执行情况。无法判断是否使用了索引你不知道查询是否真正使用了索引还是进行了全集合扫描。// 执行统计模式executionStatsdb.products.explain(executionStats).find({quantity:{$gt:50},category:apparel})executionStats模式的优势显示执行统计包括扫描文档数、使用索引情况、执行时间等。能判断是否使用了索引通过indexOnly字段判断。墨瑾轩的血泪教训有一次我给一个项目用了db.collection.explain().find()结果查询还是慢。我当场就懵了——这特么不是’explain’吗怎么还这么慢后来才发现我只用了queryPlanner模式没有看到执行统计。优化后我直接换成executionStats模式发现查询在进行全集合扫描不是使用索引。// 全计划执行模式allPlansExecutiondb.products.explain(allPlansExecution).findAndModify({query:{name:Tom,state:active,rating:{$gt:10}},sort:{rating:1},update:{$inc:{score:1}}})allPlansExecution模式的优势显示所有计划不仅显示获胜计划还显示被拒绝的计划。能对比计划性能通过executionStats字段对比不同计划的执行情况。为什么这个模式是复明的关键揭示查询优化器的选择为什么MongoDB选择了这个计划而不是那个计划。找到真正的性能瓶颈不是所有查询都像你想象的那样。2. explain输出的5个关键指标如何快速定位索引问题explain()返回的输出包含大量信息但只有5个关键指标能帮你快速定位索引问题。别被海量数据迷惑了1. cursor: 索引类型与名称cursor:BtreeCursor quantity_1,解读BtreeCursor表示使用了B树索引。quantity_1表示索引名称quantity是字段名1表示升序。为什么这个指标重要确认是否使用了索引如果cursor是BasicCursor表示没有使用索引进行了全集合扫描。查看索引名称通过索引名称可以查看系统索引信息。2. n: 返回文档数量n:1,解读返回的文档数量。为什么这个指标重要与nscanned对比如果n很小但nscanned很大说明查询扫描了大量文档但只返回少量可能索引不够高效。3. nscanned: 扫描文档数量nscanned:100,解读查询扫描的文档数量。为什么这个指标重要衡量索引效率nscanned越小越好理想情况是等于n。发现索引问题如果nscanned远大于n说明索引不够高效。4. indexOnly: 是否使用了索引覆盖indexOnly:true,解读true表示查询只使用了索引没有回表查询文档。为什么这个指标重要判断索引覆盖如果indexOnly为true表示索引覆盖了查询所需的所有字段不需要回表查询文档性能最好。优化索引如果indexOnly为false说明需要回表查询文档可能需要创建覆盖索引。5. millis: 查询执行时间millis:5,解读查询执行的毫秒数。为什么这个指标重要衡量查询性能millis越小越好。对比优化效果优化前后的millis对比能直观看到优化效果。墨瑾轩的血泪教训有一次我看到一个查询的millis是500ms但nscanned是100000n是10。我当场就懵了——这特么不是’快’吗后来才发现虽然millis看起来还行但nscanned远大于n说明索引不够高效。优化后我创建了覆盖索引nscanned从100000降到10millis从500ms降到5ms。3. 实战案例从500ms到5ms的性能飞跃现在让我们看一个真实的优化案例从500ms到5ms的性能飞跃。原始查询全集合扫描db.users.find({gender:M,age:{$gt:30}})explain()输出{cursor:BasicCursor,isMultiKey:false,n:50,nscanned:100000,nscannedObjects:100000,scanAndOrder:false,indexOnly:false,nYields:0,nChunkSkips:0,millis:500,indexBounds:{}}问题分析cursor是BasicCursor表示没有使用索引进行了全集合扫描。nscanned是100000n是50说明扫描了10万文档只返回50个索引效率极低。millis是500ms查询性能差。优化方案创建覆盖索引db.users.createIndex({gender:1,age:1})优化后查询db.users.find({gender:M,age:{$gt:30}})explain()输出{cursor:BtreeCursor gender_1_age_1,isMultiKey:false,n:50,nscanned:50,nscannedObjects:50,scanAndOrder:false,indexOnly:true,nYields:0,nChunkSkips:0,millis:5,indexBounds:{gender:[[M,M]],age:[[30,maxElement]]}}优化后分析cursor是BtreeCursor表示使用了索引。nscanned是50n是50说明扫描了50个文档返回50个索引效率高。indexOnly是true表示查询只使用了索引没有回表查询文档。millis是5ms查询性能提升100倍。为什么优化后性能提升了100倍从全集合扫描到索引扫描从扫描10万文档到扫描50个文档。从回表查询到索引覆盖不需要回表查询文档减少了I/O操作。从500ms到5ms查询时间从500ms缩短到5ms。墨瑾轩的吐槽“这特么不是’优化’吗为啥我当年还傻乎乎地用全集合扫描” 优化后产品经理终于不再半夜发在吗而是发了条牛逼我差点在办公室里跳起来。4. 常见错误为什么你的explain查询还在卡死在使用explain时常见的错误会让你的查询继续卡死。以下是几个典型错误错误1忘记使用explain模式// 错误没有指定explain模式db.users.find({gender:M,age:{$gt:30}}).explain()为什么这个错误会导致查询卡死默认模式是queryPlanner没有指定模式只返回计划不返回执行统计。无法判断查询性能你不知道查询是否使用了索引执行时间如何。墨瑾轩的血泪教训有一次我看到一个查询的RT是500ms但explain()返回的只是计划没有执行统计。我当场就懵了——这特么不是’explain’吗后来才发现没有指定executionStats模式。解决方案明确指定explain模式如db.collection.explain(executionStats).find()。错误2索引创建不匹配查询// 错误索引与查询不匹配db.users.createIndex({gender:1})db.users.find({gender:M,age:{$gt:30}})为什么这个错误会导致查询卡死索引只覆盖gender字段查询需要gender和age两个字段但索引只覆盖gender。索引效率低需要回表查询文档效率低下。墨瑾轩的吐槽“这特么不是’索引’吗为啥还这么慢” 优化后我创建了复合索引{gender: 1, age: 1}查询效率提升100倍。解决方案创建与查询匹配的复合索引确保索引覆盖查询所需的所有字段。错误3忽略索引选择// 错误没有检查索引选择db.users.find({gender:M,age:{$gt:30}})为什么这个错误会导致查询卡死MongoDB可能选择了错误的索引如果存在多个索引MongoDB可能选择了效率低的索引。无法判断最佳索引你不知道MongoDB选择了哪个索引。墨瑾轩的血泪教训有一次我看到一个查询的RT是500ms但explain()返回的queryPlanner显示MongoDB选择了错误的索引。我当场就懵了——这特么不是’优化’吗后来才发现MongoDB选择了效率低的索引。解决方案使用allPlansExecution模式检查MongoDB选择的所有计划选择最佳索引。5. 高级技巧用explain动态优化索引对于更高级的优化我们可以用explain动态优化索引让MongoDB自动为你选择最佳索引。// 动态优化索引functionoptimizeQuery(collection,query){constexplainResultcollection.explain(allPlansExecution).find(query).toArray();// 选择最佳计划constbestPlanexplainResult.queryPlanner.winningPlan;// 创建最佳索引constindexFieldsObject.keys(bestPlan.inputStage.indexName);collection.createIndex(indexFields);returnbestPlan;}关键注释explain(allPlansExecution)使用allPlansExecution模式获取所有计划。queryPlanner.winningPlan获取MongoDB选择的获胜计划。inputStage.indexName获取获胜计划使用的索引字段。为什么这个高级技巧能提升性能自动选择最佳索引MongoDB会自动选择最佳索引无需手动创建。动态优化查询根据查询动态优化索引提高查询性能。墨瑾轩的吐槽“这特么不是’动态优化’吗为啥我当年还傻乎乎地手动创建索引” 优化后我们发现查询性能提升了50%索引创建更精准。尾声explain的复明之道让查询跑得像老司机现在让我们来总结一下MongoDB explain的复明之道。1. 三种explain模式queryPlanner只显示查询计划不显示执行统计。executionStats显示查询计划和执行统计推荐使用。allPlansExecution显示所有计划和执行统计用于高级优化。2. 5个关键指标cursor确认是否使用了索引。n返回文档数量。nscanned扫描文档数量。indexOnly是否使用了索引覆盖。millis查询执行时间。3. 高级技巧动态优化索引用allPlansExecution模式自动选择最佳索引。创建覆盖索引确保索引覆盖查询所需的所有字段。墨瑾轩的终极建议不要被索引更快的假象骗了索引需要正确创建才能提升性能。先分析查询用explain分析查询找出性能瓶颈。动态调整而不是静态配置根据查询动态优化索引。