MongoDB: 查询运算符:30+运算符完全指南

最后更新:2026-08-26

查询运算符是 MongoDB 查询的核心——掌握 30+ 运算符可解决 90% 的查询场景。

本课程系统讲解所有查询运算符的用法、组合方式和性能优化技巧。

1. 你将学到


2. 一个数据分析师的真实故事

(1) 痛点:复杂查询需要嵌套 if-else

Diana 是电商数据分析师,需要查找"价格 100-500、库存 > 0、标签包含 'bestseller' 或 'new'、评分 >= 4"的商品:

JAVASCRIPT
// ❌ 反例:先查询所有再过滤
const allProducts = await Product.find();
const filtered = allProducts.filter(p =>
  p.price >= 100 && p.price <= 500 &&
  p.stock > 0 &&
  (p.tags.includes('bestseller') || p.tags.includes('new')) &&
  p.rating >= 4
);
// 100 万数据,只返回 100 条,但查询了全部

(2) MongoDB 运算符的解法

JAVASCRIPT
// ✅ 正例:一次查询,用运算符组合
const products = await Product.find({
  price: { $gte: 100, $lte: 500 },
  stock: { $gt: 0 },
  tags: { $in: ['bestseller', 'new'] },
  rating: { $gte: 4 }
});
// 数据库层过滤,100 万 → 100 条,性能 ↑1000x

100%
graph TB
    A[查询运算符] --> B[比较运算符<br/>$eq/$gt/$lt]
    A --> C[逻辑运算符<br/>$and/$or/$not]
    A --> D[元素运算符<br/>$exists/$type]
    A --> E[数组运算符<br/>$all/$size/$elemMatch]
    A --> F[评估运算符<br/>$regex/$expr/$mod]

    style B fill:#d4edda
    style E fill:#d4edda

3. 比较运算符

概念说明:比较运算符是查询运算符中最基础、最常用的类别,支持等于、不等于、大于、小于、范围、多值匹配等操作。8 个比较运算符覆盖了 90% 的日常查询场景,其中 $in 和范围查询($gt/$gte/$lt/$lte 组合)是最高频使用的。

工作原理:比较运算符作用于单个字段,MongoDB 将其翻译为索引扫描或全表扫描。$eq$in 对索引最友好(可用精确匹配或多值扫描),范围查询($gt/$lt)也可使用索引但效率取决于范围大小。$ne$nin 对索引不友好——因为"不等于某个值"的文档可能分散在整个索引中。

100%
graph TB
    A[比较运算符] --> B[等值类<br/>$eq / $ne]
    A --> C[范围类<br/>$gt / $gte / $lt / $lte]
    A --> D[多值类<br/>$in / $nin]
    
    B --> B1[索引友好度: $eq ✅ / $ne ⚠️]
    C --> C1[索引友好度: ✅ 范围扫描]
    D --> D1[索引友好度: $in ✅ / $nin ⚠️]
运算符 含义 等价 SQL 索引友好 使用频率
$eq 等于 = value ⭐⭐⭐(默认)
$ne 不等于 != value ⚠️
$gt 大于 > value ⭐⭐
$gte 大于等于 >= value ⭐⭐⭐
$lt 小于 < value ⭐⭐
$lte 小于等于 <= value ⭐⭐⭐
$in 包含于数组 IN (...) ⭐⭐⭐
$nin 不包含 NOT IN (...) ⚠️

(1) 完整列表

运算符 含义 示例
$eq 等于 { price: { $eq: 599 } }
$ne 不等于 { status: { $ne: 'deleted' } }
$gt 大于 { age: { $gt: 18 } }
$gte 大于等于 { rating: { $gte: 4 } }
$lt 小于 { stock: { $lt: 10 } }
$lte 小于等于 { discount: { $lte: 0.5 } }
$in 包含于数组 { category: { $in: ['A', 'B'] } }
$nin 不包含于数组 { status: { $nin: ['deleted', 'banned'] } }

▶ 示例 1:比较运算符实战

JAVASCRIPT
// === 价格区间 ===
db.products.find({
  price: { $gte: 100, $lte: 500 }   // 100 <= price <= 500
});

// === 多值匹配 ===
db.products.find({
  category: { $in: ['Electronics', 'Books'] }
});

// === 排除特定值 ===
db.users.find({
  role: { $nin: ['banned', 'deleted'] }
});

输出:

TEXT 📖 仅展示
// mongoose 操作成功执行
// 数据库查询/更新结果

4. 逻辑运算符

概念说明:逻辑运算符组合多个查询条件,实现 AND/OR/NOT 逻辑。MongoDB 最常见的逻辑是隐式 AND——{ a: 1, b: 2 } 等价于 { $and: [{ a: 1 }, { b: 2 }] }。显式 $and 仅在"同一字段多个条件"时必须使用(如 { $and: [{ price: { $gte: 100 } }, { price: { $lte: 500 } }] })。

工作原理$and 要求所有子条件同时满足,MongoDB 优化器会选择最具选择性的条件优先执行。$or 要求任一子条件满足,MongoDB 对每个子条件独立执行查询再合并结果集——这意味着 $or 的每个子条件最好都有索引,否则会触发多次全表扫描。

100%
graph TB
    A[逻辑运算符] --> B[隐式 AND<br/>{ a: 1, b: 2 }<br/>最常用 ⭐⭐⭐]
    A --> C[$and<br/>同一字段多条件]
    A --> D[$or<br/>任一满足 ⭐⭐]
    A --> E[$not<br/>取反 ⭐]
    A --> F[$nor<br/>全部不满足]

    style B fill:#d4edda
运算符 含义 等价 SQL 注意事项
隐式 AND 逗号分隔 WHERE a=1 AND b=2 最常用写法
$and 显式 AND WHERE (a=1 AND b=2) 同字段多条件必须用
$or 任一满足 WHERE a=1 OR b=2 每个子条件应有索引
$not 不满足 WHERE NOT (cond) 常配合 $regex 使用
$nor 全部不满足 WHERE NOT (a=1 OR b=2) 少用

(1) 完整列表

运算符 含义
$and 全部满足
$or 任一满足
$not 不满足
$nor 全部不满足

▶ 示例 2:逻辑运算符实战

JAVASCRIPT
// === 隐式 $and ===
db.products.find({
  category: 'Electronics',
  stock: { $gt: 0 }
  // 等同于 { $and: [{ category: 'Electronics' }, { stock: { $gt: 0 } }] }
});

// === 显式 $and(同一字段多个条件)===
db.products.find({
  $and: [
    { price: { $gte: 100 } },
    { price: { $lte: 500 } }
    // 必须用 $and,因为同一字段不能直接写多个条件
  ]
});

// === $or ===
db.products.find({
  $or: [
    { category: 'Electronics' },
    { tags: 'bestseller' }
  ]
});

// === $nor(都不匹配)===
db.products.find({
  $nor: [
    { category: 'Books' },
    { stock: 0 }
  ]
});
// 返回既不是 Books 分类、又有库存的商品

// === 嵌套逻辑 ===
db.products.find({
  $and: [
    { category: 'Electronics' },
    {
      $or: [
        { tags: 'bestseller' },
        { rating: { $gte: 4.5 } }
      ]
    }
  ]
});

输出:

TEXT 📖 仅展示
// mongoose 操作成功执行
// 数据库查询/更新结果

5. 元素运算符

概念说明:元素运算符检查文档中字段的存在性和类型——$exists 判断字段是否存在,$type 判断字段的 BSON 类型。这两个运算符在数据质量检查和脏数据清理中至关重要:$exists 可以找出缺失必填字段的文档,$type 可以找出类型错误的字段(如 price 被错误存为 string 而非 number)。

工作原理$exists: true 匹配包含指定字段的所有文档(包括字段值为 null 的文档),$exists: false 匹配不包含该字段的文档。$type 按 BSON 类型码或类型名称匹配,支持数组形式(如 { price: { $type: ["number", "decimal"] } } 匹配 Double 或 Decimal128)。

运算符 含义 典型场景
$exists: true 字段存在 找出有 discount 字段的商品
$exists: false 字段不存在 找出缺失 email 的用户
$type: "string" 字段类型为字符串 找出 price 被错误存为 string 的文档
$type: ["number", "decimal"] 多种类型之一 找出 price 为任意数值类型的文档

(1) $exists 检查字段是否存在

JAVASCRIPT
// === 字段存在 ===
db.products.find({ discount: { $exists: true } });
// 有 discount 字段的商品

// === 字段不存在 ===
db.users.find({ lastLoginAt: { $exists: false } });
// 从未登录的用户

// === 配合其他条件 ===
db.products.find({
  discount: { $exists: true, $ne: null }
});
// 有 discount 且不为 null

(2) $type 检查字段类型

JAVASCRIPT
// === 类型码对照 ===
// 1: Double, 2: String, 3: Object, 4: Array
// 5: Binary, 7: ObjectId, 8: Boolean, 9: Date
// 10: Null, 16: Int32, 18: Long, 19: Decimal128

// === 字符串类型 ===
db.products.find({ sku: { $type: "string" } });

// === 数值类型(多种)===
db.products.find({ price: { $type: ["number", "decimal"] } });
// Double 或 Decimal128

// === 实际应用:找出类型错误的字段 ===
db.products.find({ price: { $type: "string" } });
// 找出 price 字段被错误存为字符串的文档(数据迁移常见问题)

▶ 示例 3:元素运算符实战

JAVASCRIPT
// === 查找缺失字段的脏数据 ===
db.users.find({
  $or: [
    { email: { $exists: false } },
    { username: { $exists: false } }
  ]
});

// === 类型修复 ===
db.products.find({ price: { $type: "string" } }).forEach(doc => {
  db.products.updateOne(
    { _id: doc._id },
    { $set: { price: NumberDecimal(doc.price) } }
  );
});

输出:

TEXT 📖 仅展示
// mongoose 操作成功执行
// 数据库查询/更新结果

6. 数组运算符

概念说明:数组运算符是 MongoDB 查询运算符中最独特的类别,专门处理数组字段——$all 匹配包含所有指定元素的数组,$size 匹配特定长度的数组,$elemMatch 匹配数组中同时满足多个条件的单个元素。其中 $elemMatch 是最容易出错也是最重要的数组运算符。

工作原理:MongoDB 的数组查询默认是"任意元素匹配"——{ tags: "5g" } 匹配 tags 数组中包含 "5g" 的文档。但多条件查询时,{ "reviews.rating": { $gte: 4 }, "reviews.content": /good/ } 可能匹配到不同的数组元素(一条评论高评分,另一条含关键词)。$elemMatch 解决了这个问题——确保同一数组元素同时满足所有条件。

100%
graph TB
    A[数组运算符] --> B[$all<br/>包含所有指定元素]
    A --> C[$size<br/>数组长度精确匹配]
    A --> D[$elemMatch<br/>同元素多条件匹配<br/>⭐ 最重要]

    D --> D1[点表示法<br/>不同元素可能匹配]
    D --> D2[$elemMatch<br/>同一元素必须匹配]

    style D fill:#d4edda
    style D2 fill:#d4edda
运算符 含义 语法 注意事项
$all 包含所有指定元素 { tags: { $all: ["5g", "amoled"] } } 顺序无关
$size 数组长度精确匹配 { tags: { $size: 3 } } 不支持范围,用 $expr
$elemMatch 同一元素多条件匹配 { reviews: { $elemMatch: { ... } } } 区别于点表示法

(1) $all 匹配所有元素

JAVASCRIPT
// === 必须包含所有指定元素(顺序无关)===
db.products.find({ tags: { $all: ['5g', 'amoled'] } });
// 同时包含 5g 和 amoled 的商品

// === 等同于 $and ===
db.products.find({
  $and: [
    { tags: '5g' },
    { tags: 'amoled' }
  ]
});

(2) $size 匹配数组长度

JAVASCRIPT
// === 数组长度精确匹配 ===
db.products.find({ tags: { $size: 3 } });
// 标签数量恰好为 3

// === 不支持范围,需用 $expr ===
db.products.find({
  $expr: { $gt: [{ $size: "$tags" }, 3] }
});
// 标签数量 > 3

(3) $elemMatch 元素匹配(关键!)

JAVASCRIPT
// === 场景:查询评论中评分 >= 4 且含关键词 "good" 的评论 ===
db.products.find({
  reviews: {
    $elemMatch: {
      rating: { $gte: 4 },
      content: /good/i
    }
  }
});
// ✅ 正确:单条评论同时满足两个条件

// === 反例:不使用 $elemMatch ===
db.products.find({
  "reviews.rating": { $gte: 4 },
  "reviews.content": /good/i
});
// ⚠️ 错误:可能匹配到不同评论(一条评论高评分,另一条含关键词)

▶ 示例 4:数组运算符实战

JAVASCRIPT
// === 场景:学生选课系统 ===
db.students.insertMany([
  {
    name: "Alice",
    courses: [
      { name: "Math", score: 85 },
      { name: "Physics", score: 92 },
      { name: "Chemistry", score: 78 }
    ]
  },
  // ...
]);

// 查询:选了 Math 且分数 >= 90 的学生
db.students.find({
  courses: {
    $elemMatch: { name: "Math", score: { $gte: 90 } }
  }
});

// 查询:选了 3 门以上课程的学生
db.students.find({
  $expr: { $gte: [{ $size: "$courses" }, 3] }
});

// 查询:所有课程分数都 >= 80 的学生
db.students.find({
  courses: {
    $not: {
      $elemMatch: { score: { $lt: 80 } }
    }
  }
});

输出:

TEXT 📖 仅展示
// mongoose 操作成功执行
// 数据库查询/更新结果

7. 评估运算符

概念说明:评估运算符提供了更灵活的查询能力——$regex 支持正则表达式匹配(模糊搜索),$expr 支持使用聚合表达式进行字段间比较,$mod 支持取模运算。这些运算符功能强大但性能较差,应作为"最后手段"使用——优先考虑索引友好的查询方式。

工作原理$regex 默认无法使用索引(除了前缀锚定 ^pattern),需要扫描所有文档执行正则匹配。$expr 使用聚合管道表达式,对每个文档计算表达式结果再比较,性能也不佳。两者都不应作为高频查询的主要条件。

运算符 含义 索引友好 性能 适用场景
$regex 正则匹配 ⚠️ 仅前缀锚定 模糊搜索
$expr 聚合表达式 很慢 字段间比较
$mod 取模运算 取余数筛选

(1) $regex 正则表达式

JAVASCRIPT
// === 简单正则 ===
db.products.find({ title: /^iPhone/ });
// 标题以 "iPhone" 开头

// === 不区分大小写 ===
db.products.find({ title: { $regex: 'phone', $options: 'i' } });

// === 多模式匹配 ===
db.products.find({
  title: { $regex: 'phone|mobile', $options: 'i' }
});

// === 性能优化:正则锚定(避免 ^.* 通配)===
// ✅ 快:db.products.find({ title: /^iPhone/ })
// ⚠️ 慢:db.products.find({ title: /iPhone/ })

(2) $expr 使用聚合表达式

JAVASCRIPT
// === 字段间比较 ===
db.products.find({
  $expr: { $gt: ["$discount", "$price"] }
});
// discount > price(异常数据)

// === 数组长度比较 ===
db.products.find({
  $expr: { $gte: [{ $size: "$tags" }, 5] }
});

// === 字符串操作 ===
db.products.find({
  $expr: { $eq: [{ $substr: ["$title", 0, 5] }, "Hello"] }
});

(3) $mod 取模

JAVASCRIPT
// === 字段值对 N 取模 ===
db.products.find({ price: { $mod: [100, 0] } });
// 价格能被 100 整除

// === 价格对 100 取模 = 50 ===
db.products.find({ price: { $mod: [100, 50] } });

8. 综合实战

概念说明:实际业务中的查询很少只使用单个运算符——通常是多个运算符的组合。组合查询的关键原则是:将最具选择性的条件放在前面(减少后续条件的扫描量),为所有 $or 子句的字段建立索引,避免在 $expr 中使用复杂的聚合表达式。

性能优化原则

  1. 等值查询($eq/$in)选择性最高,优先放在 filter 中
  2. 范围查询($gt/$lt)选择性次之,放在等值条件之后
  3. $or 的每个子句应有独立索引支持
  4. $regex 仅前缀锚定可使用索引,其他情况全表扫描
  5. $expr 性能最差,作为最后手段使用
100%
graph TB
    A[综合查询优化] --> B[1. 等值过滤<br/>选择性最高]
    A --> C[2. 范围过滤<br/>选择性次之]
    A --> D[3. 逻辑组合<br/>$and/$or]
    A --> E[4. 排序 + 分页<br/>sort + limit]
    
    B --> B1[{ category: 'E' }<br/>索引扫描]
    C --> C1[{ price: { $gte: 100 } }<br/>范围扫描]
    D --> D1[多个条件组合<br/>合并结果集]
    E --> E1[索引排序<br/>避免内存排序]

    style B1 fill:#d4edda
优化策略 查询方式 索引要求 性能
最优 等值 + 索引排序 复合索引 O(log N)
良好 等值 + 范围 + 索引 复合索引 O(log N + K)
一般 $or + 各子句索引 多个独立索引 O(K * log N)
较差 $regex 前缀锚定 前缀索引 O(log N + K)
最差 $expr / 无索引排序 O(N log N)

(1) 复杂查询示例

JAVASCRIPT
// === 场景:电商高级搜索 ===
// 价格 100-1000、Electronics 或 Computers、库存 > 0、
// 标签包含 'bestseller' 或 'new'、评分 >= 4、有 discount
db.products.find({
  $and: [
    { price: { $gte: 100, $lte: 1000 } },
    {
      $or: [
        { category: 'Electronics' },
        { category: 'Computers' }
      ]
    },
    { stock: { $gt: 0 } },
    { tags: { $in: ['bestseller', 'new'] } },
    { rating: { $gte: 4 } },
    { discount: { $exists: true, $ne: null } }
  ]
});

▶ 示例 5:运算符性能对比

JAVASCRIPT
// === 性能测试:100 万文档 ===

// ❌ 慢:未索引字段 + $exists
db.users.find({ lastLoginAt: { $exists: true } });
// ~500ms(全表扫描)

// ✅ 快:索引字段 + 范围查询
db.users.find({ age: { $gte: 18, $lt: 30 } });
// ~5ms(索引扫描)

// ❌ 慢:正则未锚定
db.products.find({ title: /phone/i });
// ~300ms(全表扫描 + 正则匹配)

// ✅ 快:正则锚定
db.products.find({ title: /^iPhone/ });
// ~10ms(前缀索引)

输出:

TEXT 📖 仅展示
// mongoose 操作成功执行
// 数据库查询/更新结果

❓ 常见问题

Q $in 和 $or 有什么区别?
A $in 是语法糖,等同于 $or。{ field: { $in: [A, B] } } 等同于 { $or: [{ field: A }, { field: B }] }。性能相同,推荐 $in 更简洁。
Q $elemMatch 和点表示法有什么区别?
A { "a.b": 1, "a.c": 2 } 可能匹配不同数组元素的 b 和 c;{ a: { $elemMatch: { b: 1, c: 2 } } } 必须同一元素同时满足。
Q 为什么正则查询慢?
A 默认正则无法使用索引(除了前缀 ^pattern)。非锚定正则需扫描所有文档执行正则匹配,性能差。
Q $expr 什么时候用?
A 需要字段间比较或使用聚合函数时(如 $size、$substr、$cond)。但 $expr 性能差,尽量用普通查询。

📖 小节


📝 作业

  1. 基础题(⭐):查询价格 100-500、库存 > 0 的 Electronics 商品。
  2. 基础题(⭐):用 $in 查询多个分类(Electronics/Books/Clothing)的商品。
  3. 进阶题(⭐⭐):用 $elemMatch 查询评论中"评分 >= 4 且含 'good' 关键词"的商品。
  4. 进阶题(⭐⭐):用 $expr 查询标签数量 > 3 且评分 >= 4 的商品。
  5. 挑战题(⭐⭐⭐):构建复杂查询(5+ 运算符组合),包含 AND/OR/NOT、$elemMatch、$regex 锚定,测试性能差异。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏