MongoDB explain 里 COLLSCAN、SORT 怎么排查?让 Codex 走 TaoToken 对着 executionStats 看

发布时间:2026/9/18 10:49:38
MongoDB explain 里 COLLSCAN、SORT 怎么排查?让 Codex 走 TaoToken 对着 executionStats 看
在 mongosh 里执行db.products.explain(executionStats).find({ quantity: { $gt: 50 }, category: apparel })之后返回的 JSON 常常长到一屏放不下而真正让人卡住的不是字段数量是眼睛先看到COLLSCAN和SORT却不知道它们分别挂在哪一级阶段、该先动索引还是先动排序。MongoDB explain 的排障更稳的顺序是先保存完整输出再打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 注册 TaoToken、创建一把 API Key把 Codex 接到 TaoToken 的模型通道让 Codex 只根据你贴出的queryPlanner.winningPlan和executionStats做逐项对照。TaoToken 只提供 Key 和 Base URL不连接你的 MongoDB查询、建索引、复跑 explain 都在本地 mongosh 完成。1. 先复现db.products.explain(executionStats) 输出里只盯三个位置1.1 在 mongosh 里保存完整 explain 输出排障最怕的是只截一张图queryPlanner和executionStats各看一半。先固定一个查询把它完整跑出来。下面这条命令对应原文里executionStats模式的示例只是把字段和格式改成更容易保存的形式use shop db.products.explain(executionStats).find({ quantity: { $gt: 50 }, category: apparel }).pretty()如果怀疑优化器在多个计划之间犹豫用allPlansExecution模式再看一次。原文也提到只有这个模式才会在结果里加入allPlansExecution字段db.products.explain(allPlansExecution).update( { quantity: { $lt: 1000 }, category: apparel }, { $set: { reorder: true } } )注意explain 包裹的写操作不会真正修改文档它只是让 MongoDB 把准备执行的计划暴露出来。把两次输出都存到本地文件后面不管是自己对照还是贴给 Codex 分析都不用来回翻终端。1.2 第一眼只看 queryPlanner.winningPlan.stagequeryPlanner里最重要的是winningPlan。它是一棵树根节点是最终产出结果的阶段中间节点处理子节点传来的文档或索引键叶节点负责访问集合或索引。你看到COLLSCAN时它通常出现在叶节点你看到SORT时它往往在根节点或靠近根节点的位置说明排序没有交给索引完成。不要一上来就逐字段读。先找winningPlan.stage再顺着inputStage往下看。如果某一层出现COLLSCAN基本可以确定这个分支没有走索引如果某一层出现SORT再回头看排序字段和已有索引的字段顺序是否对得上。1.3 第二眼只看 executionStats 的 totalDocsExamined / totalKeysExaminedexecutionStats是给获胜计划补执行数据的。最值得先看的是nReturned查询条件最终匹配到多少文档。totalKeysExamined扫描了多少索引键。totalDocsExamined扫描了多少文档。executionTimeMillis计划选择加执行的总耗时。如果totalDocsExamined远大于nReturned说明大量文档被扫出来又被过滤掉。如果totalKeysExamined很大而totalDocsExamined很小可能是覆盖查询也可能索引选择性不够。把这三个数字和阶段树放在一起看比单独问“为什么慢”有用得多。2. 阶段树拆解COLLSCAN、IXSCAN、FETCH、SORT 谁是谁的父节点2.1 叶节点、中间节点、根节点MongoDB 把查询计划展开成阶段树。叶节点访问集合或索引例如COLLSCAN或IXSCAN中间节点对子节点产生的文档或索引键做过滤、取文档、合并根节点是最终把结果集交给客户端的阶段。理解这一点之后FETCH就不会再显得突兀它通常是IXSCAN的父节点表示先从索引拿到位置再回集合取完整文档。SORT的位置更值得盯。如果排序字段能被索引顺序满足阶段树里通常不会出现SORT一旦出现说明 MongoDB 需要在内存里对结果重排。数据量小的时候看不出来数据量一大SORT加上COLLSCAN往往就是慢查询的组合拳。2.2 常见阶段速查表阶段含义典型位置排查关注COLLSCAN集合扫描叶节点查询条件是否缺少可用索引IXSCAN索引扫描叶节点索引边界、方向、字段顺序FETCH回集合取文档中间节点是否可以通过覆盖查询去掉SHARD_MERGE合并分片结果根附近分片集合才出现SHARDING_FILTER过滤孤立文档中间节点分片集合才出现LIMIT限制返回数量根附近能否让索引提前停止扫描PROJECTION限定返回字段中间节点是否只返回必要字段IDHACK按_id精确查询叶节点通常很快COUNTcount 运算根附近看是否退化成 COUNTSCANCOUNTSCANcount 未用索引叶节点需要改成 COUNT_SCANCOUNT_SCANcount 使用索引叶节点希望看到SUBPLA未用索引的$or中间节点各分支分别看索引TEXT全文索引查询叶节点全文索引场景AND_SORTED/AND_HASH索引交集中间节点看inputStagesOR$or使用索引中间节点看各分支是否 IXSCAN2.3 希望看到与不希望看到的阶段原文最后给出过一个很实用的清单希望看到FetchIDHACK、FetchIXSCAN、LimitFetchIXSCAN、PROJECTIONIXSCAN、SHARDING_FILTERIXSCAN、COUNT_SCAN。不希望看到COLLSCAN、无索引的SORT、不合理的SKIP、SUBPLA、COUNTSCAN。这个清单不是让你背术语而是让你在 explain 输出里快速分类看到IXSCAN先别高兴太早继续看有没有FETCH和大totalDocsExamined看到COLLSCAN也别立刻加索引先确认查询条件、字段类型、排序和分页是否让索引失效。3. queryPlanner.winningPlan 里 COLLSCAN 和 SORT 的定位方法3.1 winningPlan、inputStage、inputStages、rejectedPlanswinningPlan是优化器选中的计划。只有一个子阶段时用inputStage有多个子阶段时用inputStages。例如$or或索引交集会产生多个输入源。rejectedPlans是被拒绝的候选计划数组没有其他候选时可能为空。排查COLLSCAN时先确认它出现在winningPlan还是rejectedPlans。如果只在rejectedPlans里说明优化器没选它问题不大如果在winningPlan的叶节点才需要继续深挖。排查SORT时先看它是不是根节点再看它的inputStage是IXSCAN还是COLLSCAN。如果是IXSCAN但仍有SORT大概率是索引字段顺序不满足排序。3.2 出现 COLLSCAN 时先查什么先看查询条件字段有没有索引。常见情况是category和quantity都出现在查询里但只给其中一个建了单字段索引或者索引字段顺序和查询模式不匹配。再检查字段类型字符串字段用数字去查或者数字字段用字符串去查都可能让索引用不上。如果查询里有$or优先看是否出现SUBPLA。SUBPLA表示$or的某些分支没有走索引。可以把每个分支单独 explain确认哪个分支缺索引。如果查询里有正则表达式也要注意前缀匹配和全文索引的区别。3.3 出现 SORT 时先查什么先看排序字段。假设查询按createdAt倒序再按category过滤那么索引里等值字段通常放在前面排序字段放在后面。如果索引只有category没有createdAt排序就可能退化成内存SORT。再看分页。skip很大时即使有索引MongoDB 也可能需要扫描大量索引键再跳过。原文把“不合理的 SKIP”列入不希望看到的阶段就是提醒你分页方式可能比索引本身更拖后腿。4. executionStatstotalDocsExamined 和 totalKeysExamined 到底看哪个4.1 nReturned、executionTimeMillis、totalKeysExamined、totalDocsExaminedexecutionStats描述获胜计划的完整执行信息。nReturned是最终返回数量executionTimeMillis是计划选择加执行的总时间。totalKeysExamined是扫描的索引条目数totalDocsExamined是扫描的文档数。判断该看哪个取决于阶段树。如果叶节点是COLLSCANtotalKeysExamined通常为 0 或很小totalDocsExamined才是重点。如果叶节点是IXSCAN先看totalKeysExamined是否接近集合总量再看totalDocsExamined是否被FETCH放大。两个数字都大说明索引选择性差且回表多。4.2 executionStages 里的 keysExamined、docsExamined、works、advanced、needTime、isEOFexecutionStages是带执行数据的阶段树。每个阶段可能有keysExamined、docsExamined、works、advanced、needTime、needYield、isEOF。works是工作单元数advanced是返回给父阶段的结果数needTime是没有产出中间结果的工作循环数isEOF表示是否到达流末尾。不要只盯着顶层。LIMIT阶段可能已经isEOF: 1但底层IXSCAN仍然isEOF: 0。这说明查询虽然限制了返回数量索引扫描并没有提前停止。把每层的keysExamined和docsExamined对齐看才能找到真正的扫描来源。4.3 覆盖查询与 FETCHtotalDocsExamined0 意味着什么如果IXSCAN不是FETCH的后代并且totalDocsExamined为 0通常说明索引覆盖了查询MongoDB 只靠索引键就能匹配条件并返回结果不需要回集合取文档。原文在兼容性修改里也提到旧版本用indexOnly表示覆盖查询新版本要看阶段树和totalDocsExamined。覆盖查询是优化方向之一但不是唯一目标。如果为了覆盖查询把太多字段塞进索引写入成本和索引体积也会上升。先解决COLLSCAN和大SORT再考虑覆盖。4.4 分片集合的 shards 输出别漏看集合分片时queryPlanner.winningPlan.shards会按分片列出计划信息executionStats.executionStages.shards会按分片列出执行统计。不要只看顶层汇总否则某个分片单独COLLSCAN或单独SORT会被平均掉。serverInfo会给出 host、port、version、gitVersion。排查时顺手确认版本因为 3.0 之后 explain 格式变化很大旧文章里的cursor、nscanned、nscannedObjects、scanAndOrder已经对应到新字段。5. 把 explain JSON 交给 CodexTaoToken 的 Key 与 ~/.codex/config.toml5.1 准备 Key打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建本地 explain 输出保存好之后打开 TaoToken 注册并创建 API Key。Key 用占位符YOUR_API_KEY表示不要把它写进文章、截图或公开仓库。模型 ID 不要从旧文章里抄以 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 模型广场当时列出的可用 ID 为准。这里要分清楚两个地址官网落地页用于注册、创建 Key、看模型广场、看用量填进 Codex 的 Base URL 是https://taotoken.net/api末尾不要加/v1也不要填官网地址。5.2 Codex 的 config.toml 写 Base URL https://taotoken.net/apiCodex 的配置文件通常在~/.codex/config.toml。把自定义供应商指向 TaoToken 的 API 通道示例写成这样# ~/.codex/config.toml model YOUR_MODEL_ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api chat然后让环境变量在当前终端生效export TAOTOKEN_API_KEYYOUR_API_KEYWindows PowerShell 里可以这样$env:TAOTOKEN_API_KEYYOUR_API_KEYYOUR_MODEL_ID换成模型广场里实际可用的模型 ID。base_url只写到https://taotoken.net/api多写/v1或写成官网落地页都可能导致请求路径不对。配置完成后Codex 负责解读你贴出的 explain 输出TaoToken 只负责提供 Key 和 Base URL不连接 MongoDB。5.3 给 Codex 的提问模板只贴 winningPlan 和 executionStats不要对 Codex 说“你连上我的 MongoDB 跑一下”。它没有你的数据库连接也不应该去连。正确的做法是先在本地 mongosh 执行把输出贴回对话。提问可以按这个结构下面是我在本地 mongosh 执行 db.products.explain(executionStats).find({ quantity: { $gt: 50 }, category: apparel }) 得到的片段。 不要假设你能访问数据库。 queryPlanner.winningPlan 粘贴 winningPlan executionStats 粘贴 executionStats 请按阶段树逐层解释 1. COLLSCAN 或 SORT 出现在哪一层 2. 对照“不希望看到阶段”检查 SUBPLA、COUNTSCAN、不合理 SKIP 3. 说明该重点看 totalDocsExamined 还是 totalKeysExamined 4. 给出索引字段顺序建议和本地复跑命令。这样问Codex 的输出会围绕queryPlanner和executionStats而不是泛泛讲“加索引就好了”。6. 本地复跑验证IXSCAN、LIMIT、PROJECTION、COUNT_SCAN 有没有出现6.1 调整索引后重新 explain假设 Codex 对照输出后建议复合索引先不要盲信回到本地 mongosh 执行。示例db.products.createIndex({ category: 1, quantity: 1 })然后重新跑同一条 explaindb.products.explain(executionStats).find({ quantity: { $gt: 50 }, category: apparel }).pretty()重点看winningPlan里是否出现IXSCANSORT是否消失totalDocsExamined是否下降。如果排序字段不是quantity索引尾字段要按实际排序调整不要照抄。索引字段顺序错了IXSCAN可能还是会出现但SORT依旧在。6.2 如果还是 COLLSCAN 或 SORT继续让 Codex 对照 rejectedPlans复跑后如果结果不理想把新的winningPlan、executionStats和rejectedPlans一起贴回对话让 Codex 对比两次阶段树差异。尤其要指出rejectedPlans里有没有更优计划以及优化器为什么没选它。这个对比过程比单次 explain 更有价值。6.3 分片集合的验证不要只看一个分片如果集合分片复跑后要展开executionStages.shards逐个分片看IXSCAN、COLLSCAN、SORT。某个分片的数据分布可能让计划完全不同顶层汇总看不出来。把分片名、阶段、totalDocsExamined一起贴给 Codex让它按分片整理排查顺序。7. SUBPLA、COUNTSCAN、不合理 SKIP 的专项排查7.1 SUBPLA$or 没用上索引SUBPLA通常和未使用索引的$or有关。排查时把$or拆成几个单独查询分别 explain看哪个分支出现COLLSCAN。如果每个分支都有索引但合起来还是SUBPLA再看是否可以用复合索引或索引交集。原文里OR阶段和AND_SORTED、AND_HASH阶段都值得对照重点看inputStages里每个分支是不是IXSCAN。7.2 COUNTSCAN 与 COUNT_SCANCOUNTSCAN表示 count 没有使用索引COUNT_SCAN表示 count 使用了索引。如果你在 explain 里看到COUNTSCAN先确认查询条件字段是否有索引再确认 count 是否被包装成了会扫描文档的形式。希望看到的阶段清单里明确列了COUNT_SCAN所以 count 慢查询的优化目标很直接让阶段树里出现COUNT_SCAN。7.3 skip 和分页不合理的SKIP不一定单独显示成一个阶段但它会体现在keysExamined和docsExamined上。翻到很后面的页时MongoDB 可能已经跳过了大量索引键。可以考虑基于排序字段的范围分页而不是单纯增大skip。这一点让 Codex 结合nReturned、totalKeysExamined、totalDocsExamined一起判断比只看阶段名更准。7.4 旧字段和新阶段名的对照MongoDB 3.0 之后 explain 格式变化明显。旧版本cursor.explain()里的BasicCursor、BtreeCursor 索引名、indexOnly、scanAndOrder、nscanned、nscannedObjects等字段分别对应新版本里的COLLSCAN、IXSCAN、覆盖查询、SORT、totalKeysExamined、totalDocsExamined。如果你搜到的文章还在讲旧字段贴给 Codex 时最好注明 MongoDB 版本避免它按旧格式解释新输出。8. 收尾去控制台看这次 Codex 调用是否记上配置完成后先在 TaoToken 模型对话 里用同一把 Key 发一条测试消息确认模型 ID 和 Base URL 没填错。如果打算长期用 Codex 解读 explain、整理排查步骤可以看 Coding Plan 是否够用需要新建或轮换 Key去 控制台 API Keys 创建。官网入口仍是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 模型广场和用量都从那里进。若你后面还把同一套通道接到 Claude Code可对照 Claude Code 接入文档。回到 MongoDB 这边把复跑后的winningPlan再贴回对话比只问一句“为什么慢”有效得多。