只有首个$match在聚合管道开头且条件满足可下推要求(如等值、前缀正则、不含$expr/$where等)才能触发索引扫描;后续$match仅内存过滤,若含$regex无锚点、$expr计算、数组路径未建multikey索引或复合索引字段顺序不符esr原则,均导致索引失效。

为什么$match阶段经常不走索引?
不是所有$match都能触发索引,核心原因在于查询条件是否满足索引的“可下推”条件。MongoDB只会在$match中使用能被查询优化器直接翻译为“索引扫描”的表达式——比如等值、范围、前缀正则、$in(有限长度)、$gt/$lt等。但一旦混入$regex无前缀(如/abc/)、$where、$expr里带复杂计算,或者字段路径指向数组内嵌文档未启用多键索引,索引就会被跳过。
复合索引必须按ESR顺序组织字段
如果$match包含多个条件,索引字段顺序直接影响能否命中。ESR原则不是建议,是B树索引的物理限制:
-
Equality字段(如{status: "paid"})必须放在最左 -
Sort字段(如{createdAt: -1})紧随其后,且方向需与$sort一致 -
Range字段(如{amount: {$gt: 100}})只能放最后,且之后不能再跟其他条件
错误示例:db.orders.createIndex({ amount: 1, status: 1 }),当查询{status: "paid", amount: {$gt: 100}}时,status无法利用索引前缀,实际只用到amount部分。
正则和文本搜索要区分场景建索引
$regex只有前缀匹配(^abc)才能走普通B树索引;中间或后缀匹配(abc、abc$)必然全表扫描。这时必须换策略:
- 前缀模糊:建单字段索引 +
/^keyword/i,确认explain("executionStats")里executionStages.stage是IXSCAN - 任意位置匹配:改用
text索引 +$text操作符,但需注意$text不支持正则语法,只做词干匹配 - 高亮/编辑距离需求:别硬扛,引入Elasticsearch或Meilisearch做前置检索,MongoDB只存结果
切忌对同一字段既建B树索引又建text索引——写放大严重,且$regex和$text互不兼容。
聚合管道里$match的位置很关键
$match在聚合中越靠前,越早过滤数据,内存和CPU开销越小。但它必须放在能利用索引的位置:
- 管道开头的
$match可下推到查询层,直接走索引(前提是条件合规) - 如果前面有
$lookup或$unwind,后续$match就只能在内存里过滤,索引完全失效 - 涉及数组字段时,
$elemMatch里的$match条件,必须对应已建的多键索引,否则仍会COLLSCAN
一个容易被忽略的细节:即使$match写在第一阶段,若用了$expr引用其他字段计算(如{$expr: {$gt: ["$a", "$b"]}}),索引也会失效——因为这不是常量比较。











