mongodb的$regex查询绝大多数情况下触发全表扫描,因其b-tree索引仅支持前缀匹配(如/^abc/),无法表达任意正则语义,非前缀模式(如/abc/、/abc$/、/.abc./i)均退化为collscan。

MongoDB的$regex查询在绝大多数情况下会触发全表扫描,不是因为正则引擎本身低效,而是B-tree索引结构天然不支持任意子串匹配——它只能高效定位“已知前缀”的连续键区间。
根本原因:B-tree索引无法表达任意正则语义
数据库底层用B-tree组织索引键,它只支持有序范围查找(如 name > "Alice" AND name 正则表达式如 "john"、"/john/i" 或 "^.*john" 无法被转换成一个确定的、连续的键范围。MongoDB优化器识别到这一点后,直接放弃索引,降级为COLLSCAN。
只要正则不满足「严格前缀」条件,就必然全扫——这不是配置问题,是数据结构层面的硬限制。
哪些写法一定触发全表扫描
方法一:无锚点的子串匹配
{ name: { $regex: "john" } }
→ 不带^或$,等价于查找任意位置含"john"的文档,索引完全无法划界。
方法二:忽略大小写的i选项(未配collation)
{ name: { $regex: "john", $options: "i" } }
【字段必须已建大小写不敏感索引,否则即使正则合法,索引也跳过】
方法三:以.*开头或含$结尾
{ name: { $regex: "^.*john" } } 或 { name: { $regex: "john$" } }
前者^.*等价于无前缀;后者$要求匹配结尾,B-tree无法反向推导起始键。
如何验证是否真走了索引
第一步:在mongo shell中执行带explain的查询
db.users.explain("executionStats").find({ name: { $regex: "^John" } })
第二步:检查返回结果中的关键字段
若 "stage": "IXSCAN" 且 "totalDocsExamined" ≈ "nReturned",说明索引生效;
若 "stage": "COLLSCAN" 或 "totalDocsExamined" 接近集合总文档数,就是实打实的全表扫描。
这一步不能跳过——很多团队误以为“建了索引+写了^”就万事大吉,但explain才是唯一真相。
真正能走索引的写法
必须同时满足三个条件:
① 正则模式以^明确开头,且后面紧跟固定字符(如"^John",不是"^J.*"或"^.*John");
② 字段已创建普通B-tree索引(db.users.createIndex({ name: 1 }));
③ 未使用$i选项,或已为该字段显式创建collation索引({ collation: { locale: "en", strength: 2 } })。
例如{name: {$regex: "^John"}}可走索引;{name: {$regex: "^J.*"}}不行——.*破坏了确定前缀。











