查询
1. find 简介
1.1 find() 基本语法
语法:
javascript
db.collection.find(
<filter>,
<projection>
)
参数说明:
filter:查询条件(可选),指定要检索的文档条件projection:投影(可选),指定返回的字段
返回值 :
返回一个游标(cursor),包含所有匹配的文档。
基本示例:
javascript
// 查询所有文档
db.users.find()
// 带条件的查询
db.users.find({ name: "张三" })
// 带投影的查询(只返回指定字段)
db.users.find({}, { name: 1, age: 1, _id: 0 })
// 格式化的输出(更易读)
db.users.find().pretty()
1.2 findOne() 方法
语法:
javascript
db.collection.findOne(
<filter>,
<projection>
)
特点:
- 返回第一个匹配的文档(不是游标)
- 如果没找到,返回
null
示例:
javascript
// 返回第一个用户
var user = db.users.findOne()
print(user.name)
// 带条件的查询
var user = db.users.findOne({ age: { $gt: 18 } })
2. 查询条件
2.1 比较操作符
| 操作符 | 说明 | 示例 |
|---|---|---|
$eq |
等于(equal) | { age: { $eq: 25 } } 或 { age: 25 } |
$ne |
不等于(not equal) | { age: { $ne: 25 } } |
$gt |
大于(greater than) | { age: { $gt: 25 } } |
$gte |
大于等于(greater than or equal) | { age: { $gte: 25 } } |
$lt |
小于(less than) | { age: { $lt: 25 } } |
$lte |
小于等于(less than or equal) | { age: { $lte: 25 } } |
$in |
在数组中 | { age: { $in: [25, 30, 35] } } |
$nin |
不在数组中 | { age: { $nin: [25, 30, 35] } } |
示例:
javascript
// 查询年龄大于等于18且小于30的用户
db.users.find({ age: { $gte: 18, $lte: 30 } })
// 查询状态为 active 或 pending 的用户
db.users.find({ status: { $in: ["active", "pending"] } })
// 查询年龄不是25、30、35的用户
db.users.find({ age: { $nin: [25, 30, 35] } })
2.2 逻辑操作符
| 操作符 | 说明 | 示例 |
|---|---|---|
$and |
逻辑与 | { $and: [ { age: { $gt: 18 } }, { status: "active" } ] } |
$or |
逻辑或 | { $or: [ { age: { $lt: 18 } }, { status: "inactive" } ] } |
$not |
逻辑非 | { age: { $not: { $gt: 18 } } } |
$nor |
既不也不 | { $nor: [ { age: { $gt: 60 } }, { status: "inactive" } ] } |
示例:
javascript
// $and 示例(多个条件默认是隐式 AND)
db.users.find({ age: { $gt: 18 }, status: "active" })
// 显式 $and
db.users.find({
$and: [
{ age: { $gt: 18 } },
{ age: { $lt: 60 } },
{ status: "active" }
]
})
// $or 示例
db.users.find({
$or: [
{ age: { $lt: 18 } },
{ age: { $gt: 60 } }
]
})
// $nor 示例(年龄不在18-60之间且状态不是 inactive)
db.users.find({
$nor: [
{ age: { $gt: 18, $lt: 60 } },
{ status: "inactive" }
]
})
2.3 元素操作符
| 操作符 | 说明 | 示例 |
|---|---|---|
$exists |
字段是否存在 | { email: { $exists: true } } |
$type |
字段的类型 | { age: { $type: "int" } } |
示例:
javascript
// 查询有 email 字段的用户
db.users.find({ email: { $exists: true } })
// 查询 email 字段不存在的用户
db.users.find({ email: { $exists: false } })
// 查询 age 字段类型为整数的用户
db.users.find({ age: { $type: "int" } })
// 查询 age 字段类型为数字(整数或浮点数)的用户
db.users.find({ age: { $type: "number" } })
2.4 正则表达式查询
语法:
javascript
{ field: /pattern/ }
{ field: { $regex: "pattern", $options: "i" } }
常用选项:
i:不区分大小写m:多行匹配x:忽略空白字符s:点号(.)匹配所有字符(包括换行)
示例:
javascript
// 查询名字以"张"开头的用户
db.users.find({ name: /^张/ })
// 查询邮箱包含"example.com"的用户(不区分大小写)
db.users.find({ email: { $regex: "example\\.com", $options: "i" } })
// 使用正则表达式对象
db.users.find({ name: { $regex: "^张", $options: "i" } })
3. 特定类型的查询
3.1 数组查询
示例数据:
javascript
db.articles.insertMany([
{ title: "MongoDB 教程", tags: ["mongodb", "database", "nosql"], comments: [
{ user: "张三", text: "很好" },
{ user: "李四", text: "不错" }
]},
{ title: "Python 教程", tags: ["python", "programming"], comments: [
{ user: "王五", text: "实用" }
]}
])
3.1.1 精确匹配整个数组
javascript
// 匹配 tags 完全等于 ["mongodb", "database"] 的文档
db.articles.find({ tags: ["mongodb", "database"] })
3.1.2 匹配数组中的任意元素
javascript
// 匹配 tags 包含 "mongodb" 的文档
db.articles.find({ tags: "mongodb" })
// 匹配 tags 包含 "mongodb" 或 "python" 的文档
db.articles.find({ tags: { $in: ["mongodb", "python"] } })
3.1.3 数组操作符
| 操作符 | 说明 | 示例 |
|---|---|---|
$all |
包含所有指定元素 | { tags: { $all: ["mongodb", "database"] } } |
$size |
数组长度 | { tags: { $size: 3 } } |
$elemMatch |
匹配数组中的元素 | { comments: { $elemMatch: { user: "张三" } } } |
示例:
javascript
// 同时包含 mongodb 和 database 标签
db.articles.find({ tags: { $all: ["mongodb", "database"] } })
// 恰好有2个标签的文章
db.articles.find({ tags: { $size: 2 } })
// 查询张三评论过的文章
db.articles.find({
comments: {
$elemMatch: { user: "张三", text: { $exists: true } }
}
})
3.2 嵌套文档查询
示例数据:
javascript
db.users.insertMany([
{
name: "张三",
address: {
city: "北京",
street: "长安街",
zip: "100000"
}
},
{
name: "李四",
address: {
city: "上海",
street: "南京路",
zip: "200000"
}
}
])
查询方式:
javascript
// 点号表示法查询嵌套字段
db.users.find({ "address.city": "北京" })
// 精确匹配整个嵌套文档(顺序和字段必须完全一致)
db.users.find({
address: {
city: "北京",
street: "长安街",
zip: "100000"
}
})
// 使用 $elemMatch 查询嵌套文档数组
db.articles.find({
comments: {
$elemMatch: { user: "张三", text: /好/ }
}
})
3.3 查询 Null 值
注意事项:
null查询会同时匹配字段值为null的文档和字段不存在的文档
javascript
// 查询 age 为 null 的文档(包括没有 age 字段的文档)
db.users.find({ age: null })
// 只查询 age 字段存在且为 null 的文档
db.users.find({ age: { $type: 10 } }) // BSON 类型 10 是 null
// 或使用
db.users.find({ age: { $exists: true, $eq: null } })
4. $where 查询
4.1 基本概念
$where 允许使用 JavaScript 表达式或函数进行查询,提供了最大灵活性,但性能较差(需要执行 JavaScript 引擎)。
语法:
javascript
db.collection.find({ $where: <JavaScript expression> })
db.collection.find(<query>, { $where: <function> })
4.2 使用示例
javascript
// 查询 name 长度大于 2 的用户
db.users.find({ $where: "this.name.length > 2" })
// 使用函数形式
db.users.find({
$where: function() {
return this.name.length > 2 && this.age > 18
}
})
// 比较两个字段
db.products.find({ $where: "this.price > this.cost" })
// 复杂业务逻辑
db.orders.find({
$where: function() {
// 只返回总金额大于 1000 且包含特定商品的订单
var total = this.items.reduce((sum, item) => sum + item.price * item.qty, 0)
var hasLaptop = this.items.some(item => item.name === "笔记本")
return total > 1000 && hasLaptop
}
})
4.3 注意事项与性能优化
| 方面 | 说明 |
|---|---|
| 性能 | $where 无法使用索引,每个文档都需要执行 JavaScript |
| 安全性 | 避免注入攻击,不要直接拼接用户输入 |
| 替代方案 | 尽可能使用普通查询操作符($gt、$regex 等)代替 $where |
| 限制 | 在分片集群中,$where 查询会在所有分片执行 |
推荐做法:
javascript
// 不推荐(无法使用索引)
db.users.find({ $where: "this.age > 18" })
// 推荐(可以使用索引)
db.users.find({ age: { $gt: 18 } })
5. 游标
5.1 游标的基本概念
定义 :find() 方法返回的不是查询结果本身,而是一个游标(cursor),它指向结果集的指针。
游标特点:
- 惰性执行:只有在需要时才从数据库获取数据
- 可迭代:支持在 shell 中自动迭代
- 可链式调用:支持多个方法连续调用
5.2 游标方法
5.2.1 limit() - 限制返回数量
javascript
// 只返回前5条文档
db.users.find().limit(5)
// 限制返回数量为10条
db.products.find({ price: { $gt: 100 } }).limit(10)
5.2.2 skip() - 跳过文档
javascript
// 跳过前10条,返回之后的文档
db.users.find().skip(10)
// 分页查询(每页20条,第3页)
db.users.find().skip(40).limit(20)
5.2.3 sort() - 排序
语法:
javascript
db.collection.find().sort({ field1: 1, field2: -1 })
// 1 表示升序,-1 表示降序
示例:
javascript
// 按年龄升序排序
db.users.find().sort({ age: 1 })
// 多字段排序:先按年龄降序,再按姓名升序
db.users.find().sort({ age: -1, name: 1 })
// 使用索引的排序更高效
db.users.createIndex({ age: 1 })
db.users.find().sort({ age: 1 }).limit(10) // 可以使用索引
5.2.4 count() - 计数
javascript
// 统计所有文档数
db.users.find().count()
// 统计满足条件的文档数
db.users.find({ age: { $gt: 18 } }).count()
// 更精确的计数(考虑 skip 和 limit)
db.users.find({ age: { $gt: 18 } }).skip(10).limit(20).count(true)
// 推荐使用 countDocuments(更准确)
db.users.countDocuments({ age: { $gt: 18 } })
5.3 游标迭代方法
5.3.1 forEach() - 遍历游标
javascript
db.users.find().forEach(function(doc) {
print("用户: " + doc.name + ", 年龄: " + doc.age)
})
5.3.2 toArray() - 转换为数组
javascript
// 将游标转换为数组(适用于结果集较小的场景)
var users = db.users.find().limit(100).toArray()
print(users[0].name)
5.3.3 next() 和 hasNext()
javascript
var cursor = db.users.find()
while (cursor.hasNext()) {
var doc = cursor.next()
print(doc.name)
}
5.4 游标生命周期与配置
| 特性 | 说明 |
|---|---|
| 默认超时 | 游标默认在 10 分钟无活动后自动关闭 |
| 无超时游标 | db.users.find().noCursorTimeout() 可禁用超时(需手动关闭) |
| 批量获取 | 默认每批返回 101 条文档或 1MB 数据 |
| 批大小调整 | db.users.find().batchSize(50) |
示例:
javascript
// 创建无超时游标(用于长时间迭代)
var cursor = db.users.find().noCursorTimeout()
// 手动关闭游标
cursor.close()
// 设置批大小为 100
db.users.find().batchSize(100)
5.5 投影(Projection)
投影用于控制返回的字段,减少网络传输量
javascript
// 只返回 name 和 age 字段(排除 _id)
db.users.find({}, { name: 1, age: 1, _id: 0 })
// 排除某些字段(返回所有字段除了 password)
db.users.find({}, { password: 0 })
// 嵌套字段投影
db.users.find({}, { "address.city": 1, name: 1 })
// 数组切片(返回前3个元素)
db.articles.find({}, { comments: { $slice: 3 } })
// 数组切片(跳过前2个,返回3个)
db.articles.find({}, { comments: { $slice: [2, 3] } })
6. 综合示例与最佳实践
6.1 分页查询实现
javascript
// 分页查询函数
function paginate(collection, page, pageSize, filter = {}, sort = {}) {
var skip = (page - 1) * pageSize
return collection
.find(filter)
.sort(sort)
.skip(skip)
.limit(pageSize)
.toArray()
}
// 使用示例:查询第3页,每页10条,按年龄降序
var users = paginate(db.users, 3, 10, { status: "active" }, { age: -1 })
6.2 复杂查询示例
javascript
// 查询活跃用户中,年龄在18-30岁之间,且邮箱已验证,按注册时间倒序
db.users.find({
status: "active",
age: { $gte: 18, $lte: 30 },
emailVerified: true,
createdAt: { $exists: true }
}).sort({ createdAt: -1 }).limit(20)
// 查询订单中,包含特定商品且总金额大于500的订单
db.orders.find({
items: { $elemMatch: { productId: "P1001", quantity: { $gt: 0 } } },
totalAmount: { $gt: 500 }
})
6.3 查询优化建议
| 优化点 | 说明 | 示例 |
|---|---|---|
| 使用索引 | 为常用查询字段创建索引 | db.users.createIndex({ age: 1, status: 1 }) |
| 限制返回字段 | 使用投影减少数据传输 | db.users.find({}, { name: 1 }) |
| 避免 $where | 优先使用查询操作符 | 用 { age: { $gt: 18 } } 代替 $where |
| 使用 explain() | 分析查询性能 | db.users.find({ age: 18 }).explain("executionStats") |
| 合理使用 limit | 避免返回大量数据 | db.logs.find().sort({ time: -1 }).limit(100) |
总结
本章详细介绍了 MongoDB 的查询功能,涵盖:
- find 方法的基本使用和投影
- 多种查询条件:比较、逻辑、元素、正则表达式
- 特定类型查询:数组、嵌套文档、null 值的处理
- $where 查询的灵活性与性能权衡
- 游标操作:limit、skip、sort、count 以及迭代方法
掌握这些查询技术是高效使用 MongoDB 的基础,合理的查询设计和索引优化能显著提升应用性能。