MongoDB数据库常用查询语句速查手册完整版
MongoDB查询操作速查手册整理基础到高级场景,涵盖条件查询、逻辑组合、正则搜索、嵌套文档与数组查询、投影分页、字段更新、数值增减、数组操作及聚合分组统计,提供完整写法参考。
MongoDB查询操作是日常开发中最高频接触的任务之一。本文整理了一份完整的MongoDB查询速查手册,涵盖从基础CRUD到复杂聚合分析的各类场景,帮助开发者快速定位所需写法。
一、基本检索与条件查询
1.1 条件查询
先从最基础的条件查询说起。日常开发中,最常用的就是等值查询,直接筛选出status等于"active"的文档。当然,比较运算也是基础操作,比如查找金额大于100的订单,或者金额在100到500之间的记录。MongoDB还支持IN和NOT IN的查询,以及判断字段是否存在或判断字段类型,这些都可以通过$in、$nin、$exists和$type来实现。
// ===== 等值 =====
db.users.find({ status: "active" })
// ===== 比较 =====
db.orders.find({ amount: { $gt: 100 } }) // >
db.orders.find({ amount: { $gte: 100 } }) // >=
db.orders.find({ amount: { $lt: 500 } }) // <
db.orders.find({ amount: { $gte: 100, $lte: 500 } }) // 100-500
// ===== IN / NOT IN =====
db.products.find({ category: { $in: ["电子", "家居"] } })
db.products.find({ status: { $nin: ["废弃", "下架"] } })
// ===== 不等于 =====
db.users.find({ level: { $ne: "guest" } })
// ===== 存在/不存在 =====
db.users.find({ email: { $exists: true } }) // 有 email 字段
db.users.find({ phone: { $exists: false } }) // 没有 phone 字段
// ===== 类型判断 =====
db.orders.find({ amount: { $type: "double" } }) // 金额为 double 类型
db.orders.find({ amount: { $type: ["int", "long", "double"] } })
1.2 逻辑组合
实际业务中,往往需要组合多个条件,这就涉及逻辑运算符。AND操作在MongoDB中默认就是逗号分隔,多个条件自动取交集。OR和NOR则分别对应取并集和全部不满足的场景。NOT则用于取反,比如查询未满18岁的用户。组合使用也很常见,例如同时满足状态、渠道和金额的过滤条件。
// AND (隐式, 逗号分隔)
db.users.find({ status: "active", age: { $gte: 18 } })
// OR
db.users.find({ $or: [
{ status: "active" },
{ vipLevel: { $gte: 3 } }
]})
// NOR (全部不满足)
db.users.find({ $nor: [
{ status: "banned" },
{ deleted: true }
]})
// NOT
db.users.find({ age: { $not: { $gte: 18 } } }) // 未满 18
// 组合
db.orders.find({
status: "completed",
$or: [{ channel: "app" }, { channel: "web" }],
amount: { $gt: 100 }
})
1.3 正则与模糊搜索
正则表达式在文本搜索中非常实用,比如查找姓“张”的用户,或者公司名称中包含“科技”的文档。MongoDB支持正则字面量写法,也支持$regex操作符,可以指定大小写不敏感等选项,非常适合做模糊匹配。
// 以 "张" 开头
db.users.find({ name: /^张/ })
// 包含 "科技"
db.users.find({ company: /科技/ })
// 不区分大小写
db.products.find({ name: /iphone/i })
// 以 .com 结尾
db.users.find({ email: /.com$/ })
// $regex 写法
db.users.find({ name: { $regex: "^张", $options: "i" } })
1.4 嵌套文档与数组查询
处理嵌套文档和数组是MongoDB的强项。对于嵌套文档,使用点号访问路径来定位字段。对于数组,可以直接查询是否包含某个元素,或者使用$all判断是否同时包含多个元素。如果数组元素是文档,$elemMatch可以精确匹配数组中的复合条件,比如查找某个商品中数量大于5的订单项。此外,还可以通过索引访问数组中的特定元素。
// ===== 嵌套文档精确匹配 =====
db.orders.find({ "address.city": "北京" })
// ===== 数组包含 =====
db.products.find({ tags: "热销" }) // tags 数组包含 "热销"
db.products.find({ tags: { $all: ["热销", "新品"] } }) // 同时包含
// ===== 数组大小 =====
db.users.find({ hobbies: { $size: 3 } }) // 正好 3 个爱好
// ===== $elemMatch: 数组元素复合条件 =====
db.orders.find({
items: { $elemMatch: { product: "SKU001", qty: { $gt: 5 } } }
})
// ===== 数组第 N 个元素 =====
db.orders.find({ "items.0.product": "SKU001" }) // 第一个元素
1.5 投影与分页
有时候我们不需要返回所有字段,可以通过投影指定只返回name和email,并排除_id。分页方面,传统方式使用skip和limit,但更推荐使用游标分页,基于_id进行查询,避免跳过大量文档带来的性能问题。排序也很灵活,支持多字段和正序/倒序。
// 只返回指定字段
db.users.find({}, { name: 1, email: 1, _id: 0 })
// 排除某些字段
db.users.find({}, { password: 0, salt: 0 })
// 分页 (skip + limit)
db.orders.find({}).skip(20).limit(10) // 第 3 页
// 游标分页 (推荐, 利用索引)
db.orders.find({ _id: { $gt: lastId } }).limit(50).sort({ _id: 1 })
// 排序
db.orders.find({}).sort({ createdAt: -1 }) // 最新在前
db.orders.find({}).sort({ status: 1, amount: -1 }) // 多字段
二、更新操作
2.1 字段更新
更新操作是写入的核心。最基本的$set用于设置或更新字段,$unset用于删除字段,$rename用于重命名字段。还有一个$setOnInsert特别实用,它只在插入新文档时生效,配合upsert选项使用,可以避免覆盖已有数据。
// 设置字段
db.users.updateOne(
{ _id: userId },
{ $set: { lastLogin: new Date(), status: "active" } }
)
// 批量更新
db.orders.updateMany(
{ status: "expired" },
{ $set: { status: "cancelled", cancelledAt: new Date() } }
)
// 删除字段
db.users.updateOne({ _id: userId }, { $unset: { tempToken: "" } })
// 重命名字段
db.products.updateMany({}, { $rename: { "oldName": "newName" } })
// 字段不存在时设置 (insert 时生效)
db.users.updateOne(
{ _id: userId },
{ $setOnInsert: { createdAt: new Date() } },
{ upsert: true })
2.2 数值增减
处理数值字段时,$inc是最常用的原子操作,比如库存扣减、积分增加。它支持正负值,实现增或减。$mul用于乘法,比如批量加税。$max和$min则用于取最大值或最小值,常用于更新最高分或最低温度。
// 原子加减
db.products.updateOne({ _id: pid }, { $inc: { stock: -1, sold: 1 } })
db.users.updateOne({ _id: uid }, { $inc: { points: 10 } })
// 乘法
db.orders.updateMany({}, { $mul: { amount: 1.13 } }) // 加 13% 税
// 取最大/最小
db.scores.updateOne({ _id: uid }, { $max: { highScore: 95 } })
db.sensors.updateOne({ _id: sid }, { $min: { minTemp: -5 } })
2.3 数组操作
数组操作是MongoDB更新中非常灵活的部分。$push向数组末尾添加元素,$addToSet在添加时自动去重。如果需要一次添加多个元素,可以用$each,还可以配合$slice限制数组长度。从数组中移除元素用$pull,支持按值或按条件移除。此外,$pop可以移除数组的首位或末位元素,更新数组中匹配的元素则通过定位符$实现。
// 推到数组末尾
db.users.updateOne({ _id: uid }, { $push: { tags: "VIP" } })
// 推到数组末尾 (去重)
db.users.updateOne({ _id: uid }, { $addToSet: { tags: "VIP" } })
// 一次加多个
db.users.updateOne({ _id: uid }, {
$push: { tags: { $each: ["新客", "首单"], $slice: -20 } } // 只保留最后 20
})
// 从数组移除
db.users.updateOne({ _id: uid }, { $pull: { tags: "VIP" } })
db.users.updateOne({ _id: uid }, { $pull: { tags: { $in: ["过期", "禁用"] } } })
// 移除数组首/尾
db.queue.updateOne({ _id: qid }, { $pop: { tasks: -1 } }) // 移除第一个
db.queue.updateOne({ _id: qid }, { $pop: { tasks: 1 } }) // 移除最后一个
// 更新数组中匹配的元素
db.orders.updateOne(
{ _id: oid, "items.sku": "SKU001" },
{ $set: { "items.$.status": "shipped" } }
)
2.4 upsert(存在则更新,不存在则插入)
upsert是一个非常实用的特性,它结合了更新和插入。如果找到匹配的文档就执行更新,如果没有找到则插入一个新文档。配合$setOnInsert,可以确保只在插入时设置某些字段,比如创建时间。
db.carts.updateOne(
{ userId: uid },
{
$setOnInsert: { userId: uid, createdAt: new Date() },
$push: { items: { sku: "SKU001", qty: 1 } }
},
{ upsert: true }
)
三、聚合查询
聚合查询是MongoDB处理复杂统计的利器,尤其在分组统计和多维度分析时非常高效。
3.1 分组统计
最简单的分组统计是按单字段计数,比如统计不同状态的订单数量。也可以按多字段分组,比如按渠道和状态联合统计,并计算总金额。按时间粒度分组是常见需求,比如按天或按月统计销售额,通过$dateToString或$year/$month操作符实现。
// 按字段分组计数
db.orders.aggregate([
{ $group: { _id: "$status", count: { $sum: 1 } } }
])
// 按多字段分组
db.orders.aggregate([
{ $group: {
_id: { channel: "$channel", status: "$status" },
count: { $sum: 1 },
totalAmount: { $sum: "$amount" }
}},
{ $sort: { totalAmount: -1 } }
])
// 按时间粒度分组 (天)
db.orders.aggregate([
{ $group: {
_id: { $dateToString: { format: "%Y-%m-%d", date: "$createdAt" } },
revenue: { $sum: "$amount" },
orders: { $sum: 1 }
}},
{ $sort: { _id: -1 } },
{ $limit: 30 }
])
// 按时间粒度分组 (月)
db.orders.aggregate([
{ $group: {
_id: {
year: { $year: "$createdAt" },
month: { $month: "$createdAt" }
},
revenue: { $sum: "$amount" }
}},
{ $sort: { "_id.year": 1, "_id.month": 1 } }
])
3.2 聚合运算符
在分组阶段,除了$sum,还可以使用$a vg、$min、$max、$first、$last等聚合运算符。$addToSet用于去重收集,$push则保留所有值形成数组。这些运算符组合起来,可以在一次分组中完成多种统计。
db.sales.aggregate([
{ $match: { date: { $gte: ISODate("2026-01-01") } } },
{ $group: {
_id: "$productId",
total: { $sum: "$revenue" }, // 求和
a vgPrice: { $a vg: "$price" }, // 平均
minPrice: { $min: "$price" }, // 最小值
maxPrice: { $max: "$price" }, // 最大值
cnt: { $sum: 1 }, // 计数
firstSale:{ $first: "$date" }, // 第一个
lastSale: { $last: "$date" }, // 最后一个
products: { $addToSet: "$sku" }, // 去重集合
allPrices: { $push: "$price" } // 不去重数组
}}
])
3.3 过滤 → 展开 → 分组流水线
一个典型的聚合流水线是:先过滤数据,再展开数组,最后分组统计。比如,统计最近一年文章中标签的使用频率,这个流程非常清晰,且易于扩展。
// 典型三步: 过滤 → 展开数组 → 分组统计
db.articles.aggregate([
// 1. 过滤: 最近一年
{ $match: { publishDate: { $gte: ISODate("2025-01-01") } } },
// 2. 展开标签
{ $unwind: "$tags" },
// 3. 统计
{ $group: { _id: "$tags", count: { $sum: 1 } } },
{ $sort: { count: -1 } },
{ $limit: 20 }
])
3.4 分桶统计
分桶统计用于将数据按区间分组,如年龄分段或金额分段。手动分桶使用$bucket,需要指定边界和默认桶。自动分桶使用$bucketAuto,只需指定桶的数量,系统会自动划分区间,适合快速探索数据分布。
// 手动分桶
db.users.aggregate([
{ $bucket: {
groupBy: "$age",
boundaries: [0, 18, 25, 35, 50, 100],
default: "未知",
output: { count: { $sum: 1 }, a vgBalance: { $a vg: "$balance" } }
}}
])
// 自动分桶
db.users.aggregate([
{ $bucketAuto: {
groupBy: "$balance",
buckets: 5,
output: { count: { $sum: 1 } }
}}
])
3.5 多维度统计(单次查询)
$facet是聚合查询中的“瑞士军刀”,它允许在单次查询中执行多个独立的聚合流水线,并返回多个结果集。比如,在同一查询中,分别统计订单按状态的分布、按渠道的分布,以及Top 10客户,非常高效。
db.orders.aggregate([
{ $match: { createdAt: { $gte: ISODate("2026-01-01") } } },
{ $facet: {
// 按状态
byStatus: [
{ $group: { _id: "$status", count: { $sum: 1 } } }
],
// 按渠道
byChannel: [
{ $group: { _id: "$channel", count: { $sum: 1 } } }
],
// Top 10 客户
topCustomers: [
{ $group: { _id: "$userId", total: { $sum: "$amount" } } },
{ $sort: { total: -1 } },
{ $limit: 10 }
]
}}
])
3.6 连表查询
连表查询通过$lookup实现,类似于SQL的JOIN。基础用法是关联两个集合,并可以指定子查询(pipeline)来过滤或投影关联的字段。多表连查也很简单,多个$lookup阶段串联即可。注意,$lookup的结果是一个数组,通常需要配合$unwind展开。
// 基础连表 (orders → users)
db.orders.aggregate([
{ $match: { status: "completed" } },
{ $lookup: {
from: "users",
localField: "userId",
foreignField: "_id",
pipeline: [
{ $project: { name: 1, tier: 1, _id: 0 } }
],
as: "user"
}},
{ $unwind: "$user" }
])
// 多表连查
db.orders.aggregate([
{ $lookup: { from: "users", localField: "userId", foreignField: "_id", as: "user" } },
{ $lookup: { from: "products", localField: "productId", foreignField: "_id", as: "product" } },
{ $unwind: "$user" },
{ $unwind: "$product" },
{ $project: {
_id: 1, amount: 1,
userName: "$user.name",
productName: "$product.name"
}}
])
3.7 去重取值
快速查看某个字段有哪些取值,可以用distinct方法,它支持带过滤条件。如果需要在聚合管道中实现类似功能,分组时使用$group按字段分组即可。
// 查询某字段有哪些取值
db.orders.distinct("status")
// 带过滤条件的去重
db.orders.distinct("channel", { createdAt: { $gte: ISODate("2026-01-01") } })
// 聚合方式去重
db.orders.aggregate([
{ $group: { _id: "$channel" } }
])
3.8 结果输出
聚合查询的结果可以直接输出到新集合,$out会完全替换目标集合,而$merge则更灵活,可以合并到已有集合,并定义匹配时的行为(合并、替换或保留)。
// 输出为新集合
db.orders.aggregate([
{ $group: { _id: "$productId", total: { $sum: "$amount" } } },
{ $out: "product_summary" } // 完全替换
])
// 合并到已有集合
db.orders.aggregate([
{ $group: { _id: "$userId", daily: { $sum: "$amount" } } },
{ $merge: {
into: "user_stats",
on: "_id",
whenMatched: "merge", // 合并
whenNotMatched: "insert" // 新文档插入
}}
])
四、文本搜索
MongoDB的文本搜索需要先创建文本索引。搜索时,支持单个词、多个词(OR逻辑)、精确短语(用双引号包裹)以及排除词(用减号)。按相关性排序是文本搜索的一个亮点,通过textScore元数据实现。
// 创建文本索引
db.articles.createIndex({ title: "text", content: "text" })
// 搜索单个词
db.articles.find({ $text: { $search: "MongoDB" } })
// 搜索多个词 (OR 逻辑)
db.articles.find({ $text: { $search: "MongoDB 聚合" } })
// 精确短语 (双引号)
db.articles.find({ $text: { $search: ""聚合管线"" } })
// 排除词 (-)
db.articles.find({ $text: { $search: "MongoDB -MySQL" } })
// 按相关性排序
db.articles.find(
{ $text: { $search: "MongoDB" } },
{ score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } })
五、统计分析
统计分析包括集合级别的统计和聚合统计。集合统计可以直接用countDocuments或estimatedDocumentCount,后者更快但只是估算。聚合统计可以一次性完成总和、平均、最大、最小和计数。按时间分布统计也很常见,比如按小时或按星期统计订单量。
// ===== 集合统计 =====
db.orders.countDocuments() // 文档总数
db.orders.countDocuments({ status: "completed" }) // 带条件
db.orders.estimatedDocumentCount() // 估算 (基于元数据,快)
// ===== 聚合统计 =====
db.orders.aggregate([
{ $match: { status: "completed" } },
{ $group: { _id: null,
totalRevenue: { $sum: "$amount" },
a vgOrder: { $a vg: "$amount" },
maxOrder: { $max: "$amount" },
orderCount: { $sum: 1 }
}}
])
// ===== 按小时/星期分布 =====
db.orders.aggregate([
{ $group: {
_id: { $hour: "$createdAt" }, // 小时 (0-23)
// _id: { $dayOfWeek: "$createdAt" }, // 星期 (1=周日)
count: { $sum: 1 }
}},
{ $sort: { _id: 1 } }
])
六、索引管理
索引是性能优化的关键。创建索引时,可以指定单字段、复合、唯一、文本或地理索引。TTL索引非常实用,可以自动删除过期数据,比如会话记录。查看索引、删除索引以及查看索引使用情况($indexStats)都是日常管理中常用的。此外,hint可以强制查询使用特定索引,用于调试或优化。
// 创建索引
db.orders.createIndex({ status: 1 }) // 单字段
db.orders.createIndex({ status: 1, createdAt: -1 }) // 复合
db.orders.createIndex({ userId: 1 }, { unique: true }) // 唯一
db.articles.createIndex({ title: "text" }) // 文本
db.locations.createIndex({ geo: "2dsphere" }) // 地理
// TTL 索引 (自动过期)
db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 })
// 查看索引
db.orders.getIndexes()
// 删除索引
db.orders.dropIndex("status_1_createdAt_-1")
db.orders.dropIndexes() // 删除所有非 _id 索引
// 查看索引使用情况
db.orders.aggregate([{ $indexStats: {} }])
// 强制使用索引
db.orders.find({ status: "completed" }).hint({ status: 1 })
七、管理命令
最后,再分享一些常用的管理命令,主要涉及查看数据库和集合、复制数据、删除文档或集合、查看执行计划(explain)以及配置慢查询日志。慢查询监控是性能调优的第一步,通过设置slowms,可以记录超过阈值的查询,方便后续分析。
# ===== 查看 =====
show dbs # 所有数据库
show collections # 当前库所有集合
db.stats() # 库统计
db.orders.stats() # 集合统计
# ===== 复制 =====
db.orders.find().forEach(function(d) { db.orders_bak.insert(d) })
# ===== 删除 =====
db.users.deleteOne({ _id: uid })
db.users.deleteMany({ status: "banned" })
db.temp.drop() # 删除集合
# ===== explain =====
db.orders.find({ status: "completed" }).explain("executionStats")
# ===== 慢查询 =====
db.setProfilingLevel(1, { slowms: 100 }) # 记录 > 100ms 的查询
db.system.profile.find().sort({ ts: -1 }).limit(5).pretty()
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
自增主键值从何而来?深入理解原理,告别只会auto_increment
KingbaseES推荐使用serial、bigserial、显式sequence或identity列实现自增主键。serial创建integer并关联序列,bigserial对应bigint;显式sequence可自定义起始值等参数;identity有generatedbydefault(允许指定值)与always(禁止)两种模式。
Linux下瀚高数据库授权文件过期及替换解决方案
在银河麒麟系统下,瀚高数据库hgdb-4 5试用授权20天到期后需替换正式授权文件。正确操作:停止服务,备份旧文件,将授权文件复制到 opt highgo hgdb-4 5 etc lic 并命名为hgdb lic,设置权限600和属主highgo:highgo,再启动服务。禁止直接修改data目录下的license info文件。
Oracle BLOB实时同步的5大技术挑战与难点解析
OracleBLOB实时同步面临分片组装、多列隔离、长事务跨窗口、事务回滚及大对象资源控制等技术挑战,必须在日志中精确还原完整字段值,才能保证源端与目标端数据完全一致,这对同步系统的稳健性提出了高要求。
MySQL禁用redo日志导致全备失败
MySQL全量备份失败是由于数据定义语言操作触发排序索引构建,禁用重做日志导致XtraBackup无法获取一致性备份。测试验证表明,优化表语句即使无数据也会触发该问题。根本原因在于排序索引构建过程跳过了重做日志记录,破坏了备份的一致性。
Kafka架构图优化与改进的全面详细步骤与实践指南
Kafka作为实时数据流处理的核心中间件,其底层架构虽已相当成熟,但在实际生产环境中,要充分发挥其性能潜力,仍需落实到具体的调优与架构改造上。核心目标可归纳为三点:如何承载更高的吞吐量、如何保障数据不丢失、以及故障发生时如何快速恢复。本文将从这几个关键方向出发,深入探讨如何真正榨干Kafka集群的性
- 热门数据榜
相关攻略
2026-07-25 22:22
2026-07-25 22:22
2026-07-25 22:22
2026-07-25 20:35
2026-07-25 20:35
2026-07-25 20:35
2026-07-25 20:35
2026-07-25 19:38
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程

