CRUD 操作
CRUD 是 Create(创建)、Read(读取)、Update(更新)、Delete(删除)的缩写,是数据库操作的基础。掌握 CRUD 操作是使用 MongoDB 的第一步,也是最重要的一步。
CRUD 操作概述
什么是 CRUD?
CRUD 代表了应用程序对数据库的四种基本操作:
| 操作 | MongoDB 方法 | 说明 |
|---|---|---|
| Create | insertOne, insertMany | 创建新文档 |
| Read | find, findOne | 查询文档 |
| Update | updateOne, updateMany, replaceOne | 更新文档 |
| Delete | deleteOne, deleteMany | 删除文档 |
MongoDB CRUD 的特点
原子性保证:在 MongoDB 中,所有写操作在单文档级别都是原子的。这意味着如果一个更新操作修改了文档中的多个字段,要么所有字段都更新成功,要么都不更新,不会出现部分更新的情况。
// 单文档操作的原子性示例
db.accounts.updateOne(
{ _id: 'account_1' },
{
$inc: { balance: -100 }, // 扣款
$push: { transactions: { // 记录交易
type: 'withdrawal',
amount: 100,
date: new Date()
}}
}
)
// 以上两个操作要么都成功,要么都不执行
自动集合创建:当向不存在的集合插入文档时,MongoDB 会自动创建该集合。
// products 集合不存在,插入后自动创建
db.products.insertOne({ name: 'iPhone', price: 999 })
插入文档(Create)
MongoDB 提供了多种插入文档的方法。
insertOne - 插入单个文档
// 语法
db.collection.insertOne(<document>)
// 示例
db.users.insertOne({
username: "zhangsan",
email: "[email protected]",
age: 28,
createdAt: new Date()
})
返回结果:
{
acknowledged: true,
insertedId: ObjectId("...")
}
insertMany - 插入多个文档
// 语法
db.collection.insertMany([<document1>, <document2>, ...])
// 示例
db.products.insertMany([
{ name: "iPhone 15", price: 999, category: "electronics" },
{ name: "MacBook Pro", price: 1999, category: "electronics" },
{ name: "AirPods Pro", price: 249, category: "audio" }
])
返回结果:
{
acknowledged: true,
insertedIds: {
'0': ObjectId("..."),
'1': ObjectId("..."),
'2': ObjectId("...")
}
}
insert - 通用插入方法
// 可以插入单个或多个文档
db.users.insert({ username: "test" })
db.users.insert([{ a: 1 }, { a: 2 }])
注意事项
- 原子性:单文档操作是原子的
- _id 字段:如果未指定,MongoDB 自动生成 ObjectId
- 集合创建:如果集合不存在,会自动创建
// 显式指定 _id
db.users.insertOne({
_id: "user001",
username: "zhangsan",
email: "[email protected]"
})
// 插入嵌套文档
db.orders.insertOne({
orderId: "ORD001",
customer: {
name: "张三",
address: {
city: "Beijing",
street: "Main St 123"
}
},
items: [
{ product: "iPhone", quantity: 1 },
{ product: "Case", quantity: 2 }
],
total: 1030.00,
status: "pending"
})
查询文档(Read)
find - 查询文档
// 语法
db.collection.find(<query>, <projection>)
// 查询所有文档
db.users.find()
// 带条件查询
db.users.find({ age: 28 })
// 只返回特定字段
db.users.find({}, { username: 1, email: 1 })
findOne - 查询单个文档
// 返回匹配的第一个文档
db.users.findOne({ username: "zhangsan" })
// 带投影
db.users.findOne({ age: 28 }, { username: 1, email: 1 })
查询条件
1. 精确匹配
// 查询 age = 28 的用户
db.users.find({ age: 28 })
// 查询嵌套字段
db.orders.find({ "customer.name": "张三" })
2. 比较操作符
| 操作符 | 说明 | 示例 |
|---|---|---|
$eq | 等于 | { age: { $eq: 25 } } |
$ne | 不等于 | { age: { $ne: 25 } } |
$gt | 大于 | { age: { $gt: 25 } } |
$gte | 大于等于 | { age: { $gte: 25 } } |
$lt | 小于 | { age: { $lt: 30 } } |
$lte | 小于等于 | { age: { $lte: 30 } } |
$in | 在数组中 | { age: { $in: [25, 30] } } |
$nin | 不在数组中 | { age: { $nin: [25, 30] } } |
// 查询年龄大于 25 的用户
db.users.find({ age: { $gt: 25 } })
// 查询年龄在某个范围内的用户
db.users.find({ age: { $gte: 20, $lte: 30 } })
// 查询特定状态的用户
db.orders.find({ status: { $in: ["pending", "processing"] } })
3. 逻辑操作符
| 操作符 | 说明 | 示例 |
|---|---|---|
$and | 逻辑与 | { $and: [{ age: { $gt: 20 } }, { age: { $lt: 30 } }] } |
$or | 逻辑或 | { $or: [{ status: "active" }, { role: "admin" }] } |
$not | 逻辑非 | { age: { $not: { $gt: 30 } } } |
$nor | 都不是 | { $nor: [{ status: "deleted" }, { banned: true }] } |
// AND: 年龄大于 20 且小于 30
db.users.find({
$and: [
{ age: { $gt: 20 } },
{ age: { $lt: 30 } }
]
})
// OR: 状态为 active 或 role 为 admin
db.users.find({
$or: [
{ status: "active" },
{ role: "admin" }
]
})
// 简化写法(MongoDB 自动使用 $and)
db.users.find({ age: { $gt: 20, $lt: 30 } })
4. 元素操作符
| 操作符 | 说明 | 示例 |
|---|---|---|
$exists | 字段是否存在 | { email: { $exists: true } } |
$type | 字段类型 | { age: { $type: "number" } } |
// 查找有 email 字段的用户
db.users.find({ email: { $exists: true } })
// 查找 email 字段为字符串类型
db.users.find({ email: { $type: "string" } })
5. 数组操作符
| 操作符 | 说明 | 示例 |
|---|---|---|
$all | 包含所有元素 | { tags: { $all: ["a", "b"] } } |
$elemMatch | 数组元素匹配 | { scores: { $elemMatch: { $gt: 80 } } } |
$size | 数组长度 | { tags: { $size: 3 } } |
// 查询 tags 包含 "admin" 和 "developer" 的用户
db.users.find({ tags: { $all: ["admin", "developer"] } })
// 查询数组中至少有一个元素大于 90 的文档
db.students.find({
scores: { $elemMatch: { $gt: 90 } }
})
// 查询 tags 数组长度为 3 的文档
db.users.find({ tags: { $size: 3 } })
投影(Projection)
投影用于指定返回哪些字段:
// 只返回 username 和 email(默认包含 _id)
db.users.find({}, { username: 1, email: 1 })
// 排除特定字段
db.users.find({}, { password: 0 })
// 嵌套字段投影
db.orders.find({}, { "customer.name": 1, total: 1 })
// 条件投影:数组中符合条件的第一个元素
db.students.find(
{ scores: { $elemMatch: { $gt: 80 } } },
{ name: 1, "scores.$": 1 }
)
排序、分页、限制
// sort: 排序(1 升序,-1 降序)
db.users.find().sort({ age: 1 }) // 按年龄升序
db.users.find().sort({ createdAt: -1 }) // 按创建时间降序
// limit: 限制返回数量
db.users.find().limit(10)
// skip: 跳过指定数量
db.users.find().skip(20)
// 分页查询:第 2 页,每页 10 条
db.users.find().sort({ createdAt: -1 }).skip(10).limit(10)
// 组合使用
db.products.find({ category: "electronics" })
.sort({ price: 1 })
.limit(20)
.skip(0)
更新文档(Update)
更新操作是 CRUD 中最复杂的部分,MongoDB 提供了丰富的更新操作符来满足各种需求。
更新操作的选择
在选择更新方法时,需要考虑以下几点:
| 方法 | 更新范围 | 说明 |
|---|---|---|
updateOne | 单个文档 | 只更新第一个匹配的文档 |
updateMany | 多个文档 | 更新所有匹配的文档 |
replaceOne | 单个文档 | 替换整个文档(保留 _id) |
updateOne - 更新单个文档
// 语法
db.collection.updateOne(<filter>, <update>, <options>)
// 示例:将用户名改为 "zhangsan" 的用户的年龄更新为 30
db.users.updateOne(
{ username: "zhangsan" },
{ $set: { age: 30 } }
)
updateMany - 更新多个文档
// 更新所有年龄大于 30 的用户的状态
db.users.updateMany(
{ age: { $gt: 30 } },
{ $set: { status: "senior" } }
)
replaceOne - 替换文档
替换整个文档(除了 _id):
db.users.replaceOne(
{ username: "zhangsan" },
{
username: "zhangsan",
email: "[email protected]",
age: 30
}
)
更新操作符详解
MongoDB 提供了多种更新操作符,每种操作符都有特定的用途。理解这些操作符是掌握 MongoDB 更新操作的关键。
$set - 设置字段值
$set 是最常用的更新操作符,用于设置字段的值。如果字段不存在,则会创建该字段。
// 更新单个字段
db.users.updateOne(
{ _id: 1 },
{ $set: { age: 30 } }
)
// 更新嵌套字段(使用点符号)
db.orders.updateOne(
{ _id: 1 },
{ $set: { "customer.address.city": "Shanghai" } }
)
// 同时更新多个字段
db.users.updateOne(
{ _id: 1 },
{ $set: {
name: "张三",
email: "[email protected]",
"profile.bio": "软件工程师"
}}
)
重要提示:$set 只更新指定的字段,不会影响文档中的其他字段。
$unset - 删除字段
$unset 用于删除文档中的字段,字段的值可以是任意值(通常使用空字符串 "" 或 1)。
// 删除单个字段
db.users.updateOne(
{ _id: 1 },
{ $unset: { age: "" } }
)
// 删除多个字段
db.users.updateOne(
{ _id: 1 },
{ $unset: { age: "", tempField: "" } }
)
// 删除嵌套字段
db.orders.updateOne(
{ _id: 1 },
{ $unset: { "customer.address": "" } }
)
$inc - 数值递增/递减
// 年龄增加 1
db.users.updateOne(
{ _id: 1 },
{ $inc: { age: 1 } }
)
// 积分减少 50
db.users.updateOne(
{ _id: 1 },
{ $inc: { points: -50 } }
)
$mul - 数值相乘
// 价格翻倍
db.products.updateMany(
{ category: "electronics" },
{ $mul: { price: 2 } }
)
$rename - 重命名字段
// 将 username 重命名为 name
db.users.updateMany(
{},
{ $rename: { "username": "name" } }
)
$push - 向数组添加元素
// 添加一个元素到 tags 数组
db.users.updateOne(
{ _id: 1 },
{ $push: { tags: "vip" } }
)
// 使用 $each 添加多个元素
db.users.updateOne(
{ _id: 1 },
{ $push: { tags: { $each: ["vip", "premium"] } } }
)
// 使用 $sort 排序后添加
db.scores.updateOne(
{ _id: 1 },
{ $push: { scores: { $each: [95, 88], $sort: -1 } } }
)
$pull - 从数组删除元素
// 删除 tags 数组中的 "old" 元素
db.users.updateOne(
{ _id: 1 },
{ $pull: { tags: "old" } }
)
// 删除大于 80 的分数
db.scores.updateOne(
{ _id: 1 },
{ $pull: { scores: { $gt: 80 } } }
)
$addToSet - 添加唯一元素
// 只添加不存在的元素
db.users.updateOne(
{ _id: 1 },
{ $addToSet: { tags: "new" } }
)
upsert - 插入或更新
如果文档不存在,则插入新文档:
// 如果没有 "testuser",则创建新用户
db.users.updateOne(
{ username: "testuser" },
{ $set: { email: "[email protected]", age: 25 } },
{ upsert: true }
)
删除文档(Delete)
deleteOne - 删除单个文档
// 删除第一个匹配的文档
db.users.deleteOne({ username: "zhangsan" })
deleteMany - 删除多个文档
// 删除所有年龄大于 100 的用户
db.users.deleteMany({ age: { $gt: 100 } })
// 删除集合中的所有文档
db.users.deleteMany({})
remove - 通用删除方法
// 删除所有匹配的文档
db.users.remove({ status: "inactive" })
// 删除单个文档
db.users.remove({ username: "zhangsan" }, true)
drop - 删除整个集合
// 删除整个集合(包括索引)
db.users.drop()
dropDatabase - 删除数据库
// 删除当前数据库
db.dropDatabase()
实战示例
完整的 CRUD 操作流程
// 1. 插入数据
db.products.insertMany([
{ name: "iPhone 15", price: 999, category: "electronics", stock: 100 },
{ name: "MacBook Pro", price: 1999, category: "electronics", stock: 50 },
{ name: "Coffee Maker", price: 89, category: "appliances", stock: 200 }
])
// 2. 查询数据
// 查询所有电子产品
db.products.find({ category: "electronics" })
// 查询价格小于 1000 的产品
db.products.find({ price: { $lt: 1000 } })
// 3. 更新数据
// iPhone 15 降价
db.products.updateOne(
{ name: "iPhone 15" },
{ $set: { price: 899, onSale: true } }
)
// 所有电子产品库存减少 10
db.products.updateMany(
{ category: "electronics" },
{ $inc: { stock: -10 } }
)
// 4. 删除数据
// 删除库存为 0 的产品
db.products.deleteMany({ stock: { $lte: 0 } })
批量操作与事务
// 批量写入(更高效)
db.products.bulkWrite([
{ insertOne: { document: { name: "Product A", price: 100 } } },
{ insertOne: { document: { name: "Product B", price: 200 } } },
{ updateOne: { filter: { name: "Product A" }, update: { $set: { price: 120 } } } },
{ deleteOne: { filter: { name: "Product C" } } }
])
小结
本章我们学习了:
- 插入文档:insertOne、insertMany、insert
- 查询文档:find、findOne,条件和投影
- 更新文档:updateOne、updateMany、replaceOne
- 删除文档:deleteOne、deleteMany、drop
这些是 MongoDB 最基础也是最重要的操作,必须熟练掌握。