复合索引前缀失效会导致查询无法利用最左字段约束,表现为totaldocsexamined极大而nreturned极小;需通过explain("executionstats")验证索引实际使用情况,重点检查stage是否为ixscan及indexbounds中是否存在minkey/maxkey全范围。

复合索引前缀失效会导致查询无法利用索引的最左字段约束,实际扫描范围远超预期,表现为executionStats.totalDocsExamined极大而nReturned极小。必须通过explain输出逐层验证索引边界是否被真正收敛。
确认索引是否被选用
运行带executionStats模式的explain命令:db.collection.explain("executionStats").find({a: 1, b: 2})。重点检查executionStages.stage字段——若为COLLSCAN,说明索引根本未参与;若为IXSCAN,继续看indexBounds和docsExamined比值。
注意:queryPlanner阶段只反映MongoDB“认为该用哪个索引”,不可信;必须依赖executionStats中的真实扫描数据。
检查索引前缀是否实际生效
在explain结果中定位executionStats.executionStages.indexBounds。假设索引为{a: 1, b: 1, c: 1},而查询条件是{a: 1, c: 3},此时你会看到:
a字段的bound是[1, 1](精确收敛),但b字段bound是[MinKey, MaxKey](全范围),c字段bound也是[3, 3]却无法下推——因为b缺失导致c脱离有效前缀链。
【b字段bound为[MinKey, MaxKey]即表明前缀断裂,c条件实际靠文档级过滤而非索引查找】
验证字段顺序是否符合ESR原则
第一步:列出当前高频查询的全部过滤与排序字段,标注类型:
• 等值条件(E):status、tenant_id
• 排序字段(S):createdAt
• 范围条件(R):amount、updatedAt
第二步:对照索引定义,确认字段顺序是否为E→S→R。例如查询{status: "paid"}→.sort({createdAt: -1})→{amount: {$gt: 100}},理想索引应为{status: 1, createdAt: -1, amount: 1}。
第三步:若现有索引是{status: 1, amount: 1, createdAt: -1},则createdAt排序将触发SORT阶段——因为范围字段amount插在了排序字段之前,破坏了索引有序性。
排查低基数字段前置是否被误用
方法一:用distinct统计字段唯一值数量。执行db.collection.distinct("field").length,若结果小于总文档数的1%,说明该字段基数极低,适合作为复合索引首字段。
方法二:检查索引键平均长度。运行db.collection.stats(),观察avgObjSize与indexDetails中各索引的size。若复合索引size异常偏大(如远超avgObjSize×文档数×1.2),可能因高基数字段前置导致前缀压缩失效。
方法三:强制重建索引并启用prefixCompression。先dropIndex,再createIndex时显式加{"prefixCompression": true},仅对{tenant_id: 1, type: 1, ts: -1}这类低→高基数顺序有效;对{ts: -1, tenant_id: 1}重建后仍无压缩收益。











