这是《MongoDB 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 查询操作符决定筛选、范围、数组、类型和模式匹配能力。操作符越强,越要注意语义、索引支持和性能边界。写查询时,应先明确返回结果,再验证执行计划。
7.1 比较操作符
| 操作符 | 说明 |
|---|---|
$eq |
等于 |
$ne |
不等于 |
$gt |
大于 |
$gte |
大于等于 |
$lt |
小于 |
$lte |
小于等于 |
$in |
在集合中 |
$nin |
不在集合中 |
等值查询:
db.orders.find({ status: "PAID" })
等价显式写法:
db.orders.find({ status: { $eq: "PAID" } })
范围查询:
db.orders.find({
created_at: {
$gte: new Date("2026-08-01T00:00:00Z"),
$lt: new Date("2026-09-01T00:00:00Z")
}
})
$in 适合少量枚举值:
db.orders.find({
status: { $in: ["CREATED", "WAIT_PAY", "PAID"] }
})
7.2 逻辑操作符
同一对象中的多个条件是 AND:
db.orders.find({
user_id: "u_1001",
status: "PAID"
})
显式 $and:
db.orders.find({
$and: [
{ user_id: "u_1001" },
{ amount: { $gte: NumberDecimal("100.00") } }
]
})
OR:
db.orders.find({
$or: [
{ status: "CLOSED" },
{ paid_at: { $lt: new Date("2026-08-01T00:00:00Z") } }
]
})
NOT:
db.orders.find({
status: { $ne: "CANCELLED" }
})
$ne 和 $nin 的选择性和索引利用通常不如正向等值条件。
7.3 存在与类型
字段存在:
db.products.find({ color: { $exists: true } })
字段不存在:
db.products.find({ color: { $exists: false } })
类型判断:
db.products.find({ price: { $type: "decimal" } })
常用于数据质量排查。生产写入应有应用对象和 validator 统一类型,避免同一字段混用字符串和数字。
7.4 数组查询
数据:
{
sku_id: "sku_10001",
tags: ["hot", "new", "office"],
attrs: [
{ name: "color", value: "black" },
{ name: "size", value: "M" }
]
}
包含一个元素:
db.products.find({ tags: "hot" })
包含全部元素:
db.products.find({ tags: { $all: ["hot", "new"] } })
数组长度:
db.products.find({ tags: { $size: 3 } })
$size 不能直接使用索引,也不支持范围;若需要按长度查询,建议冗余计数字段。
7.5 数组元素匹配
必须同时满足同一子文档中的多个条件:
db.products.find({
attrs: {
$elemMatch: {
name: "color",
value: "black"
}
}
})
普通写法:
db.products.find({
"attrs.name": "color",
"attrs.value": "black"
})
普通写法可能匹配到不同子文档的组合,语义与 $elemMatch 不同。
7.6 正则查询
前缀匹配:
db.users.find({ email: /^alice@example\.com$/ })
大小写不敏感:
db.users.find({ name: /^alice$/i })
非前缀正则:
db.users.find({ name: /alice/ })
索引支持:
- 区分大小写的前缀匹配可以利用索引;
- 大小写不敏感或非前缀匹配通常扫描更多数据;
- 高频搜索应使用文本索引或搜索引擎;
- 复杂正则会显著消耗 CPU。
7.7 模糊匹配替代方案
| 需求 | 建议 |
|---|---|
| 固定前缀 | 前缀正则或范围查询 |
| 包含关键词 | text index |
| 中文搜索 | ngram 或 Elasticsearch |
| 自动补全 | 前缀字段或搜索服务 |
| 多条件搜索 | 搜索引擎 |
业务搜索不要把 MongoDB 正则当成通用搜索引擎。
7.8 投影与分页
只取必要字段:
db.orders.find(
{ user_id: "u_1001" },
{ order_no: 1, status: 1, amount: 1, created_at: 1 }
)
游标分页:
const lastCreatedAt = new Date("2026-08-25T10:00:00Z");
const lastId = ObjectId("66cb1f0b8d0f4e51f0a8b123");
db.orders.find({
user_id: "u_1001",
$or: [
{ created_at: { $lt: lastCreatedAt } },
{ created_at: lastCreatedAt, _id: { $lt: lastId } }
]
}).sort({ created_at: -1, _id: -1 }).limit(20)
排序键应唯一,否则同一页边界可能重复或漏数据。
7.9 查询语义陷阱
| 写法 | 语义 |
|---|---|
{ field: null } |
字段值为 null 或字段不存在 |
{ field: { $exists: false } } |
字段不存在 |
{ array: value } |
数组包含 value |
{ array: { $all: [...] } } |
包含所有指定元素 |
$elemMatch |
同一数组元素满足条件 |
{} |
全部文档 |
字段缺失和 null 在业务上是否等价,必须在 Schema 契约中明确。
7.10 性能边界
需要谨慎的操作:
$nin;$ne;- 非前缀正则;
$where;- 大
$in; - 无索引排序;
- 无投影大文档读取;
- 集合扫描统计。
$where 可以执行 JavaScript,生产应避免使用,除非有严格控制。
7.11 查询安全
应用层应使用驱动提供的参数化查询结构,而不是拼接字符串。同时:
- 限制用户可查询字段;
- 限制返回条数;
- 禁止用户输入任意操作符;
- 敏感字段脱敏;
- 查询超时可控;
- 为高频查询建索引。
本章小结
查询操作符的重点是语义正确和索引友好。等值和范围优先,数组和正则要理解匹配粒度,分页要使用唯一排序键。复杂搜索和统计应交给合适的索引或搜索引擎,而不是让 MongoDB 承担所有模糊查询。
思考题
- 为什么
$ne和$nin通常性能不好? $elemMatch和普通数组条件有什么区别?- 哪些正则可以利用索引?
null与字段缺失有什么差异?- 游标分页为什么需要唯一排序键?