$regex 默认不走索引,因b-tree仅支持有序范围查找,而多数正则无法转换为有效范围;仅^开头、无i选项、字段有b-tree索引时才可能命中索引。

绝大多数 $regex 查询默认不走索引,除非你严格满足前缀匹配条件:必须以 ^ 开头、不带 i 选项、且字段上有普通 B-tree 索引。
为什么 $regex 默认全表扫描
B-tree 索引只支持有序范围查找,而正则表达式(如 "john"、"/john/i" 或 "^.*john")无法转换成有效索引范围。MongoDB 优化器看到这类模式,会直接跳过索引,走 COLLSCAN。
常见错误现象:
-
db.users.find({name: {$regex: "john"}})—— 没锚点,不走索引 -
db.users.find({name: {$regex: /john/i}})—— 带i,破坏索引二进制可比性 -
db.users.find({name: {$regex: "^.*john"}})——^.*等价于无前缀,仍全扫
用 explain("executionStats") 验证:若 "stage": "COLLSCAN" 或 "totalDocsExamined" 接近集合总数,说明没真正利用索引。
怎么写才能让 $regex 走索引
核心是把匹配逻辑收束到「已知前缀 + 后续任意」,让 MongoDB 能从索引中划出一个连续键范围。
- 必须写成
^John形式,例如{name: {$regex: "^John"}}(^John.*效果相同,但显式写^John更安全) - 字段必须已有 B-tree 索引,如
db.users.createIndex({name: 1}) - 绝对不要加
$options: "i";如果业务需大小写不敏感,应建归一化字段(如name_lower)并索引它,再查{name_lower: {$regex: "^john"}} - 前缀不能太短(如
^A),否则选择性差,优化器可能主动放弃索引
为什么 collation 索引不是“加个选项就完事”
建了 collation 索引后,查询仍不走索引,是最常踩的坑。
- 索引创建必须显式指定:
collection.create_index([("name", 1)], collation={"locale": "en", "strength": 1}) - 查询时必须传完全一致的
collation,否则 MongoDB 默认用"simple"规则,压根不匹配该索引 -
locale必须是 ICU 支持的有效 ID(如"en"、"zh"),填"en_US"在部分版本会静默失败 -
strength: 1才真正忽略大小写;strength: 2仍区分重音,不是你要的“不敏感”
别误信通配符索引能救 $regex
{"$**": 1} 或 {"profile.$**": 1} 是为动态字段结构服务的,对 $regex 查询几乎无加速作用。
- 它不会让
{$regex: "john"}变快,依然只对^john类前缀生效 - 索引覆盖所有字段,内存占用高、写入变慢,线上慎用
- explain 中若看到
"stage": "IXSCAN"但"nReturned"极低,说明索引扫了大量无效键值——这是通配符索引低效的典型表现
真正适合关键词模糊搜索的,是 $text 查询:建文本索引 db.users.createIndex({name: "text"}),再用 db.users.find({$text: {$search: "john"}}),天然支持大小写、词干和分词。











