MongoDBNotes

第 07 章:查询操作符

zjc 于 2026-01-07 发布

这是《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/ })

索引支持:

  1. 区分大小写的前缀匹配可以利用索引;
  2. 大小写不敏感或非前缀匹配通常扫描更多数据;
  3. 高频搜索应使用文本索引或搜索引擎;
  4. 复杂正则会显著消耗 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 性能边界

需要谨慎的操作:

  1. $nin
  2. $ne
  3. 非前缀正则;
  4. $where
  5. $in
  6. 无索引排序;
  7. 无投影大文档读取;
  8. 集合扫描统计。

$where 可以执行 JavaScript,生产应避免使用,除非有严格控制。

7.11 查询安全

应用层应使用驱动提供的参数化查询结构,而不是拼接字符串。同时:

  1. 限制用户可查询字段;
  2. 限制返回条数;
  3. 禁止用户输入任意操作符;
  4. 敏感字段脱敏;
  5. 查询超时可控;
  6. 为高频查询建索引。

本章小结

查询操作符的重点是语义正确和索引友好。等值和范围优先,数组和正则要理解匹配粒度,分页要使用唯一排序键。复杂搜索和统计应交给合适的索引或搜索引擎,而不是让 MongoDB 承担所有模糊查询。

思考题

  1. 为什么 $ne$nin 通常性能不好?
  2. $elemMatch 和普通数组条件有什么区别?
  3. 哪些正则可以利用索引?
  4. null 与字段缺失有什么差异?
  5. 游标分页为什么需要唯一排序键?