MongoDB 基础操作
本文档介绍 MongoDB 的 CRUD 操作、高级查询、索引和聚合框架。
数据库与集合管理
javascript
// 查看所有数据库
show dbs
// 切换/创建数据库(插入数据后才真正创建)
use mydb
// 查看当前数据库
db
// 删除数据库
db.dropDatabase()
// 创建集合(显式创建,可指定选项)
db.createCollection("users", {
capped: true, // 固定大小集合
size: 5242880, // 最大 5MB
max: 5000 // 最多 5000 个文档
})
// 查看集合
show collections
// 删除集合
db.users.drop()CRUD 操作
创建(Create)
插入单个文档:
javascript
db.inventory.insertOne({
item: "canvas",
qty: 100,
tags: ["cotton"],
size: { h: 28, w: 35.5, uom: "cm" }
})
// 返回:{ acknowledged: true, insertedId: ObjectId("...") }插入多个文档:
javascript
db.inventory.insertMany([
{ item: "journal", qty: 25, tags: ["blank", "red"] },
{ item: "mat", qty: 85, tags: ["gray"] }
])
// 返回:{ acknowledged: true, insertedCount: 2, insertedIds: {...} }插入选项:
javascript
db.inventory.insertMany(
[{ item: "book", qty: 10 }],
{ ordered: false } // 遇到错误继续插入其他文档
)读取(Read)
查询所有文档:
javascript
db.inventory.find({})
// 格式化输出
db.inventory.find({}).pretty()条件查询:
javascript
// 等于
db.inventory.find({ item: "canvas" })
// 比较操作符
db.inventory.find({ qty: { $gte: 85 } })
db.inventory.find({ qty: { $gt: 20, $lt: 100 } }) // 范围查询完整查询操作符:
| 操作符 | 描述 | 示例 |
|---|---|---|
$eq | 等于 | { qty: { $eq: 25 } } |
$ne | 不等于 | { qty: { $ne: 25 } } |
$gt/$gte | 大于/大于等于 | { qty: { $gt: 25 } } |
$lt/$lte | 小于/小于等于 | { qty: { $lt: 25 } } |
$in | 在数组中 | { status: { $in: ["A", "D"] } } |
$nin | 不在数组中 | { status: { $nin: ["A", "D"] } } |
$or | 或条件 | { $or: [{ status: "A" }, { qty: { $lt: 30 } }] } |
$and | 与条件 | { $and: [{ qty: { $gt: 10 } }, { qty: { $lt: 50 } }] } |
$not | 取反 | { qty: { $not: { $gt: 25 } } } |
$nor | 所有条件都不满足 | { $nor: [{ price: 1.99 }, { sale: true }] } |
$exists | 字段是否存在 | { item: { $exists: true } } |
$type | 字段类型 | { item: { $type: "string" } } |
$regex | 正则匹配 | { name: { $regex: /^A/ } } |
$mod | 取模运算 | { qty: { $mod: [4, 0] } } |
投影(只返回指定字段):
javascript
// 1 表示包含,0 表示排除
db.inventory.find(
{ status: "A" },
{ item: 1, status: 1, _id: 0 }
)
// 投影数组元素
db.inventory.find(
{ status: "A" },
{ item: 1, "tags.$": 1 } // 只返回第一个匹配元素
)
// 使用 $slice 限制数组元素
db.inventory.find(
{ status: "A" },
{ comments: { $slice: 5 } } // 只返回前 5 个评论
)排序与分页:
javascript
// 排序:1 升序,-1 降序
db.inventory.find().sort({ qty: -1, item: 1 })
// 分页
db.inventory.find()
.skip(10) // 跳过前 10 条
.limit(10) // 返回 10 条
// 更高效的分页(使用 _id)
db.inventory.find({ _id: { $gt: lastId } }).limit(10)计数:
javascript
// 计数文档数量
db.inventory.countDocuments({ status: "A" })
// 估算数量(更快但不精确)
db.inventory.estimatedDocumentCount()更新(Update)
更新操作符完整列表:
| 操作符 | 描述 | 示例 |
|---|---|---|
$set | 设置字段值 | { $set: { status: "active" } } |
$unset | 移除字段 | { $unset: { oldField: "" } } |
$inc | 增加数值 | { $inc: { qty: 5 } } |
$mul | 乘以数值 | { $mul: { price: 1.1 } } |
$rename | 重命名字段 | { $rename: { "oldName": "newName" } } |
$setOnInsert | 仅插入时设置 | { $setOnInsert: { createdAt: new Date() } } |
$currentDate | 设置当前日期 | { $currentDate: { updatedAt: true } } |
$min | 取最小值 | { $min: { lowScore: 50 } } |
$max | 取最大值 | { $max: { highScore: 100 } } |
数组操作符:
| 操作符 | 描述 | 示例 |
|---|---|---|
$push | 添加元素 | { $push: { tags: "new" } } |
$pull | 移除匹配元素 | { $pull: { tags: "old" } } |
$pop | 移除首/尾元素 | { $pop: { tags: 1 } } |
$addToSet | 添加唯一元素 | { $addToSet: { tags: "unique" } } |
$pullAll | 移除多个元素 | { $pullAll: { tags: ["a", "b"] } } |
更新单个文档:
javascript
db.inventory.updateOne(
{ item: "journal" },
{
$set: { "size.uom": "in" },
$inc: { qty: 5 },
$currentDate: { updatedAt: true }
}
)更新多个文档:
javascript
db.inventory.updateMany(
{ qty: { $lt: 50 } },
{ $set: { status: "low" } }
)替换文档:
javascript
// replaceOne 会替换整个文档(除 _id)
db.inventory.replaceOne(
{ item: "journal" },
{ item: "journal", qty: 100, status: "A" }
)upsert(存在则更新,不存在则插入):
javascript
db.inventory.updateOne(
{ item: "book" },
{ $set: { qty: 20, status: "A" } },
{ upsert: true }
)数组更新示例:
javascript
// 添加到数组
db.posts.updateOne(
{ _id: 1 },
{ $push: { comments: { user: "Alice", text: "Great!" } } }
)
// 添加多个元素
db.posts.updateOne(
{ _id: 1 },
{ $push: { tags: { $each: ["mongodb", "database", "nosql"] } } }
)
// 限制数组长度
db.posts.updateOne(
{ _id: 1 },
{
$push: {
logs: {
$each: ["log1", "log2"],
$slice: -10 // 只保留最后 10 个
}
}
}
)
// 更新数组中匹配的元素
db.inventory.updateOne(
{ _id: 1, "items.name": "pen" },
{ $set: { "items.$.qty": 50 } } // $ 是占位符
)
// 更新所有匹配的数组元素
db.inventory.updateOne(
{ _id: 1 },
{ $set: { "items.$[].status": "available" } }
)
// 根据条件更新数组元素
db.inventory.updateOne(
{ _id: 1 },
{ $set: { "items.$[elem].status": "sold" } },
{ arrayFilters: [{ "elem.qty": { $lt: 10 } }] }
)删除(Delete)
javascript
// 删除单个文档
db.inventory.deleteOne({ item: "paper" })
// 删除多个文档
db.inventory.deleteMany({ status: "low" })
// 删除所有文档(保留集合)
db.inventory.deleteMany({})
// 返回:{ acknowledged: true, deletedCount: 5 }高级查询
查询嵌套文档
javascript
// 精确匹配(字段顺序必须一致)
db.inventory.find({ size: { h: 14, w: 21, uom: "cm" } })
// 使用点符号查询嵌套字段
db.inventory.find({ "size.uom": "in" })
db.inventory.find({ "size.h": { $lt: 15 } })查询数组
javascript
// 精确匹配整个数组(顺序必须一致)
db.inventory.find({ tags: ["red", "blank"] })
// 匹配顺序不同的数组
db.inventory.find({ tags: { $all: ["blank", "red"] } })
// 查询包含某元素
db.inventory.find({ tags: "red" })
// 查询包含多个元素(任意一个)
db.inventory.find({ tags: { $in: ["red", "blue"] } })
// 按数组长度查询
db.inventory.find({ tags: { $size: 3 } })使用 $elemMatch
查询数组中至少有一个元素同时满足多个条件:
javascript
// instock 数组中存在 qty>10 且 qty<=20 的元素
db.inventory.find({
instock: { $elemMatch: { qty: { $gt: 10, $lte: 20 } } }
})
// 多条件组合
db.inventory.find({
instock: { $elemMatch: { qty: { $gt: 10 }, warehouse: "A" } }
})查询空值与缺失字段
javascript
// 查询字段存在且值为 null
db.inventory.find({ field: null })
// 只查询字段存在(无论值是什么)
db.inventory.find({ field: { $exists: true } })
// 只查询字段不存在
db.inventory.find({ field: { $exists: false } })
// 查询字段存在且值为 null(类型检查)
db.inventory.find({ field: { $type: 10 } })索引
索引是提升查询性能的关键工具。MongoDB 默认在 _id 字段创建唯一索引。
索引类型
| 类型 | 描述 | 创建示例 |
|---|---|---|
| 单字段索引 | 单个字段的索引 | createIndex({ item: 1 }) |
| 复合索引 | 多个字段组合 | createIndex({ item: 1, qty: -1 }) |
| 多键索引 | 数组字段索引 | createIndex({ tags: 1 }) |
| 文本索引 | 全文检索 | createIndex({ content: "text" }) |
| 地理空间索引 | 位置查询 | createIndex({ location: "2dsphere" }) |
| 哈希索引 | 哈希分片键 | createIndex({ _id: "hashed" }) |
| 通配符索引 | 动态字段索引 | createIndex({ "attributes.$**": 1 }) |
| TTL 索引 | 自动过期删除 | createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 }) |
索引操作
javascript
// 创建索引(1 升序,-1 降序)
db.inventory.createIndex({ item: 1 })
db.inventory.createIndex({ item: 1, qty: -1 })
// 创建唯一索引
db.users.createIndex({ email: 1 }, { unique: true })
// 创建稀疏索引(只索引存在该字段的文档)
db.users.createIndex({ nickname: 1 }, { sparse: true })
// 创建部分索引(条件过滤)
db.orders.createIndex(
{ status: 1 },
{ partialFilterExpression: { status: "active" } }
)
// 创建文本索引
db.articles.createIndex({ title: "text", content: "text" })
// 创建 TTL 索引(1 小时后自动删除)
db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 })
// 查看索引
db.inventory.getIndexes()
// 查看索引大小
db.inventory.totalIndexSize()
// 删除索引
db.inventory.dropIndex("item_1")
db.inventory.dropIndexes() // 删除所有索引(除 _id)查询计划分析
javascript
// 基本分析
db.inventory.find({ item: "journal" }).explain()
// 详细执行统计
db.inventory.find({ item: "journal" }).explain("executionStats")
// 查看是否使用索引
// 关键字段:winningPlan.stage = "IXSCAN" 表示使用了索引
// winningPlan.stage = "COLLSCAN" 表示全表扫描索引策略
code
复合索引设计原则(ESR 规则):
1. Equality(等值匹配)优先
2. Sort(排序)次之
3. Range(范围查询)最后
示例查询:
db.orders.find({ status: "A" }).sort({ createdAt: -1 }).limit(10)
最佳索引:
db.orders.createIndex({ status: 1, createdAt: -1 })索引使用建议:
- 为高频查询字段创建索引
- 复合索引字段顺序遵循 ESR 规则
- 避免过多索引(影响写入性能)
- 使用
explain()验证索引使用情况 - 定期使用
$indexStats监控索引使用率 - 考虑使用覆盖索引(Covered Query)
聚合框架
聚合管道用于数据处理和分析,数据依次通过各个阶段进行处理。
聚合管道流程图
code
输入文档 → $match → $group → $sort → $project → 输出结果
↓ ↓ ↓ ↓
筛选 分组 排序 重塑常用聚合阶段
| 阶段 | 描述 | 示例 |
|---|---|---|
$match | 筛选文档 | { $match: { status: "A" } } |
$group | 分组并计算 | { $group: { _id: "$dept", total: { $sum: 1 } } } |
$project | 重塑文档结构 | { $project: { name: 1, total: 1 } } |
$sort | 排序 | { $sort: { total: -1 } } |
$limit | 限制输出数量 | { $limit: 10 } |
$skip | 跳过指定数量 | { $skip: 10 } |
$unwind | 展开数组 | { $unwind: "$tags" } |
$lookup | 关联查询 | 见下文示例 |
$addFields | 添加新字段 | { $addFields: { total: { $add: ["$a", "$b"] } } } |
$replaceRoot | 替换根文档 | { $replaceRoot: { newRoot: "$details" } } |
$count | 计数 | { $count: "total" } |
$facet | 多管道并行处理 | 见下文示例 |
$bucket | 分桶统计 | 见下文示例 |
聚合累加器
| 累加器 | 描述 | 示例 |
|---|---|---|
$sum | 求和 | { $sum: "$amount" } |
$avg | 平均值 | { $avg: "$score" } |
$min/$max | 最小/最大值 | { $min: "$price" } |
$first/$last | 第一个/最后一个 | { $first: "$name" } |
$push | 添加到数组 | { $push: "$name" } |
$addToSet | 添加唯一值到数组 | { $addToSet: "$category" } |
聚合示例
基础分组统计:
javascript
db.orders.aggregate([
// 阶段1:筛选
{ $match: { status: "A" } },
// 阶段2:分组统计
{
$group: {
_id: "$cust_id",
totalAmount: { $sum: "$amount" },
avgAmount: { $avg: "$amount" },
orderCount: { $sum: 1 }
}
},
// 阶段3:排序
{ $sort: { totalAmount: -1 } },
// 阶段4:限制
{ $limit: 10 }
])关联查询($lookup):
javascript
// 类似 SQL 的 LEFT JOIN
db.orders.aggregate([
{
$lookup: {
from: "users", // 关联的集合
localField: "userId", // 本地字段
foreignField: "_id", // 外键字段
as: "userInfo" // 输出字段名
}
},
// userInfo 是数组,通常需要展开
{
$project: {
orderId: "$_id",
userId: 1,
userName: { $arrayElemAt: ["$userInfo.name", 0] }
}
}
])
// MongoDB 3.6+ 简化语法
db.orders.aggregate([
{
$lookup: {
from: "users",
let: { userId: "$userId" },
pipeline: [
{ $match: { $expr: { $eq: ["$_id", "$$userId"] } } },
{ $project: { name: 1, email: 1 } }
],
as: "userInfo"
}
}
])展开数组($unwind):
javascript
db.inventory.aggregate([
{ $unwind: "$tags" },
{ $group: { _id: "$tags", count: { $sum: 1 } } },
{ $sort: { count: -1 } }
])
// 保留空数组文档
db.inventory.aggregate([
{ $unwind: { path: "$tags", preserveNullAndEmptyArrays: true } }
])条件逻辑($cond, $switch):
javascript
db.sales.aggregate([
{
$project: {
item: 1,
discount: {
$cond: {
if: { $gte: ["$qty", 100] },
then: 0.1,
else: 0
}
}
}
}
])
// 复杂条件
db.sales.aggregate([
{
$addFields: {
grade: {
$switch: {
branches: [
{ case: { $gte: ["$score", 90] }, then: "A" },
{ case: { $gte: ["$score", 80] }, then: "B" },
{ case: { $gte: ["$score", 70] }, then: "C" }
],
default: "F"
}
}
}
}
])多管道并行处理($facet):
javascript
db.products.aggregate([
{
$facet: {
byCategory: [
{ $group: { _id: "$category", count: { $sum: 1 } } }
],
byPriceRange: [
{ $bucket: {
groupBy: "$price",
boundaries: [0, 50, 100, 200, 500],
default: "other",
output: { count: { $sum: 1 }, avgPrice: { $avg: "$price" } }
}}
],
totalStats: [
{ $group: { _id: null, total: { $sum: 1 }, avgPrice: { $avg: "$price" } } }
]
}
}
])上一篇:概述与安装
下一篇:[Node.js 操作 MongoDB](3-Node.js 操作.md)