MongoDB: 查询运算符:30+运算符完全指南
最后更新:2026-08-26
查询运算符是 MongoDB 查询的核心——掌握 30+ 运算符可解决 90% 的查询场景。
本课程系统讲解所有查询运算符的用法、组合方式和性能优化技巧。
1. 你将学到
- 比较运算符($eq/$ne/$gt/$lt/$gte/$lte/$in/$nin)
- 逻辑运算符($and/$or/$not/$nor)
- 元素运算符($exists/$type)
- 数组运算符($all/$size/$elemMatch)
- 评估运算符($regex/$expr/$mod)
- 运算符组合与性能影响
2. 一个数据分析师的真实故事
(1) 痛点:复杂查询需要嵌套 if-else
Diana 是电商数据分析师,需要查找"价格 100-500、库存 > 0、标签包含 'bestseller' 或 'new'、评分 >= 4"的商品:
// ❌ 反例:先查询所有再过滤
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 运算符的解法
// ✅ 正例:一次查询,用运算符组合
const products = await Product.find({
price: { $gte: 100, $lte: 500 },
stock: { $gt: 0 },
tags: { $in: ['bestseller', 'new'] },
rating: { $gte: 4 }
});
// 数据库层过滤,100 万 → 100 条,性能 ↑1000x
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 对索引不友好——因为"不等于某个值"的文档可能分散在整个索引中。
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:比较运算符实战
// === 价格区间 ===
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'] }
});
输出:
// 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 的每个子条件最好都有索引,否则会触发多次全表扫描。
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:逻辑运算符实战
// === 隐式 $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 } }
]
}
]
});
输出:
// 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 检查字段是否存在
// === 字段存在 ===
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 检查字段类型
// === 类型码对照 ===
// 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:元素运算符实战
// === 查找缺失字段的脏数据 ===
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) } }
);
});
输出:
// mongoose 操作成功执行
// 数据库查询/更新结果
6. 数组运算符
概念说明:数组运算符是 MongoDB 查询运算符中最独特的类别,专门处理数组字段——$all 匹配包含所有指定元素的数组,$size 匹配特定长度的数组,$elemMatch 匹配数组中同时满足多个条件的单个元素。其中 $elemMatch 是最容易出错也是最重要的数组运算符。
工作原理:MongoDB 的数组查询默认是"任意元素匹配"——{ tags: "5g" } 匹配 tags 数组中包含 "5g" 的文档。但多条件查询时,{ "reviews.rating": { $gte: 4 }, "reviews.content": /good/ } 可能匹配到不同的数组元素(一条评论高评分,另一条含关键词)。$elemMatch 解决了这个问题——确保同一数组元素同时满足所有条件。
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 匹配所有元素
// === 必须包含所有指定元素(顺序无关)===
db.products.find({ tags: { $all: ['5g', 'amoled'] } });
// 同时包含 5g 和 amoled 的商品
// === 等同于 $and ===
db.products.find({
$and: [
{ tags: '5g' },
{ tags: 'amoled' }
]
});
(2) $size 匹配数组长度
// === 数组长度精确匹配 ===
db.products.find({ tags: { $size: 3 } });
// 标签数量恰好为 3
// === 不支持范围,需用 $expr ===
db.products.find({
$expr: { $gt: [{ $size: "$tags" }, 3] }
});
// 标签数量 > 3
(3) $elemMatch 元素匹配(关键!)
// === 场景:查询评论中评分 >= 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:数组运算符实战
// === 场景:学生选课系统 ===
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 } }
}
}
});
输出:
// mongoose 操作成功执行
// 数据库查询/更新结果
7. 评估运算符
概念说明:评估运算符提供了更灵活的查询能力——$regex 支持正则表达式匹配(模糊搜索),$expr 支持使用聚合表达式进行字段间比较,$mod 支持取模运算。这些运算符功能强大但性能较差,应作为"最后手段"使用——优先考虑索引友好的查询方式。
工作原理:$regex 默认无法使用索引(除了前缀锚定 ^pattern),需要扫描所有文档执行正则匹配。$expr 使用聚合管道表达式,对每个文档计算表达式结果再比较,性能也不佳。两者都不应作为高频查询的主要条件。
| 运算符 | 含义 | 索引友好 | 性能 | 适用场景 |
|---|---|---|---|---|
$regex |
正则匹配 | ⚠️ 仅前缀锚定 | 慢 | 模糊搜索 |
$expr |
聚合表达式 | ❌ | 很慢 | 字段间比较 |
$mod |
取模运算 | ❌ | 慢 | 取余数筛选 |
(1) $regex 正则表达式
// === 简单正则 ===
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 使用聚合表达式
// === 字段间比较 ===
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 取模
// === 字段值对 N 取模 ===
db.products.find({ price: { $mod: [100, 0] } });
// 价格能被 100 整除
// === 价格对 100 取模 = 50 ===
db.products.find({ price: { $mod: [100, 50] } });
8. 综合实战
概念说明:实际业务中的查询很少只使用单个运算符——通常是多个运算符的组合。组合查询的关键原则是:将最具选择性的条件放在前面(减少后续条件的扫描量),为所有 $or 子句的字段建立索引,避免在 $expr 中使用复杂的聚合表达式。
性能优化原则:
- 等值查询(
$eq/$in)选择性最高,优先放在 filter 中 - 范围查询(
$gt/$lt)选择性次之,放在等值条件之后 $or的每个子句应有独立索引支持$regex仅前缀锚定可使用索引,其他情况全表扫描$expr性能最差,作为最后手段使用
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) 复杂查询示例
// === 场景:电商高级搜索 ===
// 价格 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:运算符性能对比
// === 性能测试: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(前缀索引)
输出:
// mongoose 操作成功执行
// 数据库查询/更新结果
❓ 常见问题
{ field: { $in: [A, B] } } 等同于 { $or: [{ field: A }, { field: B }] }。性能相同,推荐 $in 更简洁。{ "a.b": 1, "a.c": 2 } 可能匹配不同数组元素的 b 和 c;{ a: { $elemMatch: { b: 1, c: 2 } } } 必须同一元素同时满足。^pattern)。非锚定正则需扫描所有文档执行正则匹配,性能差。📖 小节
- 比较运算符:$eq/$ne/$gt/$lt/$gte/$lte/$in/$nin
- 逻辑运算符:$and(隐式)/ $or / $not / $nor
- 元素运算符:$exists(字段存在)/ $type(字段类型)
- 数组运算符:$all(包含所有)/ $size(长度)/ $elemMatch(同元素匹配)
- 评估运算符:$regex(正则)/ $expr(聚合表达式)/ $mod(取模)
- 性能优化:索引字段查询 / 正则前缀 / 避免 $expr
📝 作业
- 基础题(⭐):查询价格 100-500、库存 > 0 的 Electronics 商品。
- 基础题(⭐):用 $in 查询多个分类(Electronics/Books/Clothing)的商品。
- 进阶题(⭐⭐):用 $elemMatch 查询评论中"评分 >= 4 且含 'good' 关键词"的商品。
- 进阶题(⭐⭐):用 $expr 查询标签数量 > 3 且评分 >= 4 的商品。
- 挑战题(⭐⭐⭐):构建复杂查询(5+ 运算符组合),包含 AND/OR/NOT、$elemMatch、$regex 锚定,测试性能差异。