正则表达式本身不触发分片路由,mongos仅依据查询是否含完整分片键前缀来定向;若缺失分片键(如仅{name: {$regex: "^alice"}})或前缀不全,必转为广播查询。

正则表达式本身不能触发分片路由,但如果你的分片键包含可被^前缀匹配的字段,且查询条件同时满足分片键路由 + 正则索引条件,就能实现“定向 + 索引”双优化。
为什么$regex查着查着就变广播查询了
mongos 路由决策只看查询条件能否唯一确定分片,不看里面有没有$regex。一旦你没带分片键(或其完整前缀),哪怕只写了一个{name: {$regex: "^Alice"}},mongos 也根本不知道该发给哪个 shard——只能 scatter/gather。常见错误包括:
- 分片键是
{tenantId: 1, createdAt: 1},但查询只传了{name: {$regex: "^Alice"}}(完全没提分片键) - 分片键是
{region: 1, userId: 1},查询写了{userId: "u123", name: {$regex: "^Alice"}}(缺region,前缀不全) - 用了
$options: "i",导致字段类型隐式转换,让 mongos 认为该字段无法参与哈希路由计算
explain("executionStats")里怎么确认是不是真定向了
别只看stage: IXSCAN,重点盯三个字段:
-
executionStages.shards:值必须是1,大于 1 就是广播 -
executionStages.executionTimeMillisEstimate:只在某一个 shard 上有明显耗时,其余为 0 或极低 -
executionStats.totalDocsExamined和nReturned的比值:如果相差 10 倍以上,说明索引只做了前缀定位,后续正则剩余部分仍在内存过滤——这时即使定向了,性能也不一定好
执行示例:
db.users.explain("executionStats").find({ region: "us", userId: "u123", name: { $regex: "^Ali" } })
分片键字段上做$regex前缀查询的硬性前提
想让region这个分片键字段既参与路由、又走索引,必须全部满足:
- 查询中显式带上
region等值条件(不能是{$gt: "us"}这类范围) -
region字段在集合上有单字段索引(db.users.createIndex({region: 1})),且不能是复合索引的非首字段 -
$regex必须用字符串形式,如{region: {$regex: "^us-"}},不能写/^us-/ - 绝对不要加
$options: "i"——大小写不一致会导致哈希值错位,mongos 无法匹配 config server 里的分片范围元数据 - 确保所有文档中
region字段类型统一为 string,混入ObjectId或 number 会让路由彻底失效
更稳的替代方案:别硬刚$regex路由
现实里真正需要“分片键字段 + 前缀模糊”的场景,往往说明原始建模有优化空间:
- 把前缀逻辑提前固化:比如
region_code: "us-west"单独存一列,建索引后直接{region_code: "us-west"}等值查,100% 定向 + 100% 索引 - 用
text索引替代(仅限搜索语义):建db.users.createIndex({name: "text"}),查{ $text: { $search: "Alice" } },虽然不支持^锚定,但能天然跨分片聚合,且 mongos 会自动下发到各 shard 并合并 - 聚合管道中强制路由:把分片键条件放在
$match最外层,再接$regex,例如[{ $match: { region: "us", name: { $regex: "^Ali" } } }, { $project: { ... } }];注意不能包在$or里,否则 mongos 放弃推导
最常被忽略的一点:分片键字段上的$regex看似能走索引,但如果该字段基数低(比如 90% 文档都是region: "us"),IXSCAN 扫出来的文档量巨大,后续正则过滤仍吃 CPU,这时候“定向”只是假象——路由对了,但没解决问题。











