$regex查"^abc"不一定走索引,因需满足字段有单字段索引、无$i$等破坏性选项、非数组/分片键外字段、不使用JS字面量,且优化器仅在纯前缀无.*+?等时重写为范围查询,否则仍COLLSCAN。

为什么 $regex 查 "^abc" 有时也不走索引?
不是写了 ^ 就一定走索引——MongoDB 对前缀正则的索引支持有硬性前提:字段必须有**单字段升序/降序索引**,且查询中不能带破坏索引利用的选项或结构。
-
$options: "i"(忽略大小写)会让索引失效,除非你显式建了带collation: { locale: "en", strength: 2 }的索引,并在查询中完全复用该 collation - 字段是数组类型(如
tags: ["web", "dev"]),即使查{tags: {$regex: "^web"}}},MongoDB 也无法对数组元素做前缀索引扫描,只会 COLLSCAN - 分片集合中,若
$regex字段**不是分片键**,且没在分片键前缀上做等值约束,路由层无法下推,查询会广播到所有分片,即使单个分片内能走索引,整体仍是低效的 - 使用 JavaScript 风格字面量
/^abc/i在某些驱动(如旧版 PyMongo)中会被序列化为空对象或触发解析异常,最终 fallback 到全表扫
$regex 和普通 B-tree 索引的匹配机制到底是什么?
MongoDB 的 B-tree 索引不“理解”正则,它只按字典序逐条比对键值。所谓“前缀正则走索引”,本质是 MongoDB 查询优化器把 ^abc 自动重写成范围查询:{"field": {"$gte": "abc", "$lt": "abd"}}(近似),再用索引定位起始点。
- 这个重写只在
$regex是纯前缀、无修饰符(或仅"m"/"x"这类不影响匹配范围的选项)时发生 -
$options: "s"(让.匹配换行符)或"u"(Unicode 模式)会阻止重写,因为索引项不包含换行符或 Unicode 归一化信息 - 一旦正则里出现
.*、?、+或字符类如[a-z],优化器就放弃重写,直接退化为 COLLSCAN
怎么确认你的 $regex 真的走了索引?
别猜,用 .explain("executionStats") 看真实执行计划。关键看两个字段是否接近:
-
executionStats.nReturned:返回文档数 -
executionStats.totalDocsExamined:实际扫描文档数
如果后者远大于前者(比如 10 万 vs 10),说明没走索引。同时检查 executionStats.executionStages.stage —— 走索引应为 IXSCAN,不是 COLLSCAN 或 TEXT。
注意:即使 IXSCAN 出现,也要看 indexName 是否是你预期的那个索引;如果显示 "indexName" : "$**_1",那其实是 wildcard index,它对 $regex 无效,这个 IXSCAN 是假象(底层仍是 COLLSCAN)。
真正可控的前缀模糊替代方案有哪些?
与其在 $regex 上反复调优,不如换更稳定的方式:
- 业务可接受时,把前缀单独存为字段(如
name_prefix: "zhang"),建普通索引,查{name_prefix: "zhang"} - 中文场景优先用
$text索引 +{$text: {$search: "张"}},它天然支持分词和前缀扩展,且不依赖用户输入格式 - 需要支持拼音、错别字、近音字?别在 MongoDB 里硬扛,同步数据到
Elasticsearch或Meilisearch,用它们的fuzziness或typo-tolerance能力,再反查 MongoDB ID
最常被忽略的一点:索引本身不是银弹。即使 $regex 成功走 IXSCAN,若前缀区分度极低(比如大量文档都以 “user_” 开头),扫描范围仍可能覆盖几万条,响应时间照样不可控。











