$regex 与索引的关系
$regex 用正则匹配字符串字段,可以写成查询时的正则字面量,也可以用操作符形式声明模式与选项。使用时最需要记住的是性能规则:只有以 ^ 锚定前缀、且不启用忽略大小写的模式,索引才能利用前缀范围缩小扫描;其余写法一律退化为全集合逐文档匹配。
基本信息
| 写法一 | { name: /alice/i } 正则字面量,可带标志 |
|---|---|
| 写法二 | { name: { $regex: "^alice", $options: "i" } } |
| 可走索引 | 以 ^ 锚定前缀且不使用 i 选项 |
| 常用选项 | i 忽略大小写、m 多行、s 让点号匹配换行、x 忽略空白 |
| 适用版本 | MongoDB 3.6+ 行为一致 |
示例
示例
// 前缀锚定:索引可用(注意没有 i 选项)
db.users.find({ name: { $regex: "^张" } })
// 前缀加忽略大小写:即使有 ^ 也会退化为全集合扫描
db.users.find({ name: { $regex: "^zhang", $options: "i" } })
// 子串匹配:没有 ^,必然全集合扫描
db.users.find({ email: { $regex: "example\\.com" } })
// $in 里只接受正则字面量,不接受 $regex 表达式对象
db.users.find({ name: { $in: [ /^zhang/, /^wang/ ] } })
// 用 explain 确认扫描文档数与 nReturned 的比例
db.users.find({ name: /alice/i }).explain("executionStats")典型使用场景
- 前缀过滤:按姓名、单号、路径前缀筛选,能吃到索引
- 模糊包含:备注、日志文本的子串检索,需接受扫描代价
- 大小写不敏感:邮箱、产品名等场景
- 数据清洗:用正则定位格式异常记录再批量修正
- 反向筛选:$not 包正则排除测试账号与脏数据
模式与扫描行为
| 写法 | 扫描行为 | 建议 |
|---|---|---|
| /^abc/ | 可用索引前缀范围扫描 | 优先使用这种形态 |
| /^abc/i | 忽略大小写,全集合扫描 | 数据量大时改用规范化字段 |
| /abc/ | 全集合扫描 | 配合其他可索引条件缩小范围 |
| /abc$/ | 后缀匹配,无索引可用 | 考虑反向存储或外部检索 |
把用户输入直接拼进正则有两个风险:语义被用户左右(词注入),以及嵌套量词触发的灾难性回溯让服务端 CPU 飙高、请求堆积。入库前应校验输入长度与字符集,必要时改用固定模式匹配。
正则对数组字段会逐个元素匹配,命中任一元素即返回整个文档;正则也不做类型转换,数字、日期字段要用范围条件而不是正则匹配。