MongoDB默认不区分大小写的查询不会走索引,必须建带collation的索引并每次查询显式调用.collation()才能生效;否则全表扫描,explain显示COLLSCAN,性能急剧下降。

用 collation 建索引 + 显式声明才能走索引
MongoDB 默认不区分大小写的查询根本不会走索引,哪怕你建了 name: 1 的普通索引也没用。真正生效的唯一路径是:建带 collation 的索引,并且每次查询都显式调用 .collation()。否则就是全表扫描。
常见错误现象:explain() 显示 stage: COLLSCAN;mongostat 看到 qr(query rate)低但 ar(active readers)飙升;数据量一过十万,响应明显变慢。
-
db.users.createIndex({name: 1}, {collation: {locale: "en", strength: 2}})——strength: 2表示忽略大小写和重音,但保留字母/符号差异(如"cafe"和"cafe!"不同) - 后续所有等值、范围、排序操作都必须配对使用:
db.users.find({name: "ALICE"}).collation({locale: "en", strength: 2}) - 漏掉
.collation(),哪怕索引存在,MongoDB 也会退回到二进制比较,完全绕过该索引
$regex 能跑通但别当主力方案
用 {name: {$regex: "alice", $options: "i"}} 确实能查到结果,但它和 collation 索引是两条路:前者不走任何 collation 索引,后者也不加速前者。它只可能利用普通前缀索引(比如 /^alice/),对中间匹配(/.*alice.*/)基本无效。
性能影响很直接:100 万文档下,未优化的 $regex 查询平均耗时超过 2 秒;高并发时 CPU 持续拉满;无法支持 .sort({name: 1}) 高效排序。
- 如果必须用正则,优先选前缀形式:
{name: {$regex: "^alice", $options: "i"}},并为name字段建普通升序索引 - 避免在大字段(如
description)上无索引直接跑/.*keyword.*/i - PHP 中要用
MongoDB\BSON\Regex类构造,不能拼字符串,否则易被注入或解析失败
Spring Data MongoDB 里用 MongoTemplate 怎么写
很多人以为 findByNameContainingIgnoreCase() 这类方法能自动走 collation 索引,其实不能。Spring Data 默认生成的是 $regex 查询,不是带 collation 的等值查询。
要真正利用 collation 索引,必须手动构造 Query 并设置 collation:
Query query = new Query(Criteria.where("name").is("ALICE"));
query.collation(Collation.of(CollationStrength.SECONDARY).locale("en"));
List<User> result = mongoTemplate.find(query, User.class);
- 别用
TextCriteria—— 它走的是文本索引,和collation索引无关,且要求字段已建text索引 - 别依赖 Repository 接口自动生成的方法名,它们底层仍是
$regex或普通等值,不带collation - 如果字段允许为空或 null,
.is("ALICE")不会匹配null,但$regex在某些版本里行为不一致,这点容易漏测
多条件模糊查询时 collation 必须全局一致
当你要同时查 name 和 email 两个字段且都要求不区分大小写时,不能一个字段用 collation、另一个用 $regex。MongoDB 不支持混合排序规则的复合查询——只要有一个字段没配 collation,整个查询就回退到默认二进制规则,所有 collation 索引失效。
正确做法是:两个字段各自建带 collation 的单字段索引,并在查询中统一声明:
db.users.find({
name: "ALICE",
email: "BOB@EXAMPLE.COM"
}).collation({locale: "en", strength: 2})
- 复合索引(如
{name: 1, email: 1})加collation是无效的,MongoDB 不支持复合 collation 索引 - 如果某字段没有
collation索引,又必须参与等值匹配,那只能先用$regex临时兜底,但得接受性能代价 - 排序也受约束:
.sort({name: 1, email: 1})必须配相同collation,否则排序不走索引,变成内存排序


















