掌握MongoDB CRUD操作:实用命令指南
通过这份实用的命令指南,解锁MongoDB的核心CRUD操作。学习如何使用`insert`、`find`、`update`和`delete`命令高效管理数据。本文提供清晰的解释、真实案例以及创建、读取、更新和删除MongoDB集合中文档的最佳实践。无论是开发者还是管理员,这都是掌握MongoDB数据操作的必备资源。
掌握MongoDB CRUD操作:实用命令指南
MongoDB作为一种流行的NoSQL文档数据库,因其灵活性、可扩展性和高性能,成为无数现代应用的基石。与任何数据库交互的核心都是基本的创建、读取、更新和删除(CRUD)操作。掌握这些命令对于任何使用MongoDB的人来说都至关重要,无论是构建新功能的开发者,还是管理数据的管理员。
本指南使用一个小型的users集合,展示你在实际工作中会用到的MongoDB CRUD命令:从注册表单添加记录、查找支持工单背后的文档、在不覆盖文档其余部分的情况下更改字段,以及在不意外清空集合的情况下删除数据。示例使用mongosh,但相同的过滤器和更新操作符也适用于应用程序驱动程序。
前提条件
在深入命令之前,请确保你已具备:
- MongoDB已安装并运行:你可以从MongoDB官方网站下载,或使用MongoDB Atlas等云服务。
- 已安装
mongosh(MongoDB Shell):这是MongoDB的交互式JavaScript接口。
连接到MongoDB并选择数据库
首先,打开终端或命令提示符,使用mongosh连接到你的MongoDB实例:
mongosh
连接后,你将进入默认的test数据库。要切换或创建新数据库,请使用use命令:
use myDatabase;
如果myDatabase不存在,当你向其中的集合插入第一个文档时,MongoDB会隐式创建它。
创建操作(插入)
创建操作涉及向集合中添加新文档。MongoDB提供了插入单个或多个文档的方法。
1. db.collection.insertOne()
此方法向集合中插入单个文档。如果集合不存在,MongoDB会创建它。
// 选择名为'users'的集合
db.users.insertOne({
name: "Alice Smith",
age: 30,
city: "New York",
email: "[email protected]",
interests: ["reading", "hiking"]
});
输出将显示acknowledged状态和新文档的insertedId。
2. db.collection.insertMany()
使用此方法在单个操作中向集合插入多个文档。它接受一个文档数组。
db.users.insertMany([
{
name: "Bob Johnson",
age: 24,
city: "Los Angeles",
email: "[email protected]",
interests: ["coding", "gaming"]
},
{
name: "Charlie Brown",
age: 35,
city: "New York",
email: "[email protected]",
interests: ["cooking", "photography"]
},
{
name: "Diana Prince",
age: 29,
city: "London",
email: "[email protected]",
interests: ["fitness", "travel"]
}
]);
这将返回所有添加文档的insertedIds数组。
提示:如果你不提供
_id字段,MongoDB会自动为每个文档添加一个唯一的ObjectId。
读取操作(查找)
读取操作涉及从集合中查询文档。find()方法是你的主要工具。
1. db.collection.find()
a. 查找所有文档
要检索集合中的所有文档,调用不带任何参数的find():
db.users.find();
b. 使用查询过滤器查找文档
向find()传递一个查询文档以指定选择文档的条件。这相当于SQL中的WHERE子句。
// 查找来自纽约的用户
db.users.find({ city: "New York" });
// 查找年龄大于25的用户
db.users.find({ age: { $gt: 25 } });
// 查找年龄在25到35之间(不包括35)的用户
db.users.find({ age: { $gt: 25, $lt: 35 } });
// 查找兴趣包含'coding'的用户
db.users.find({ interests: "coding" });
// 查找兴趣同时包含'reading'和'hiking'的用户
db.users.find({ interests: { $all: ["reading", "hiking"] } });
// 查找名为Alice Smith或来自伦敦的用户
db.users.find({
$or: [
{ name: "Alice Smith" },
{ city: "London" }
]
});
常用查询操作符:
$eq:等于(如果未指定操作符,则为默认值)$ne:不等于$gt:大于$gte:大于或等于$lt:小于$lte:小于或等于$in:匹配数组中指定的任何值$nin:不匹配数组中指定的任何值$and、$or、$not、$nor:逻辑操作符
c. 投影:选择特定字段
要仅返回字段的子集,将投影文档作为第二个参数传递给find()。值为1表示包含该字段,0表示排除。除非明确排除,否则默认包含_id字段。
// 仅返回姓名和电子邮件,排除_id
db.users.find({ city: "New York" }, { name: 1, email: 1, _id: 0 });
d. 排序、限制和跳过
链式方法允许更复杂的查询:
// 按年龄降序排序(1为升序,-1为降序)
db.users.find().sort({ age: -1 });
// 将结果限制为2个文档
db.users.find().limit(2);
// 跳过前2个文档并返回其余部分
db.users.find().skip(2);
// 组合操作:查找来自纽约的用户,按年龄排序,跳过1个,限制1个
db.users.find({ city: "New York" }).sort({ age: 1 }).skip(1).limit(1);
2. db.collection.findOne()
此方法返回匹配查询条件的单个文档。如果多个文档匹配,则返回找到的第一个。
db.users.findOne({ name: "Alice Smith" });
更新操作
更新操作修改集合中的现有文档。你可以更新单个文档、多个文档,甚至替换整个文档。
1. db.collection.updateOne()
更新匹配过滤条件的单个文档。
// 将Alice的城市更新为'San Francisco'
db.users.updateOne(
{ name: "Alice Smith" },
{ $set: { city: "San Francisco", lastUpdated: new Date() } }
);
2. db.collection.updateMany()
更新所有匹配过滤条件的文档。
// 将所有纽约用户的年龄增加1
db.users.updateMany(
{ city: "New York" },
{ $inc: { age: 1 } }
);
// 向尚未拥有'reading'兴趣的用户添加该兴趣
db.users.updateMany(
{}, // 应用于所有文档
{ $addToSet: { interests: "reading" } }
);
// 从Bob Johnson的兴趣中移除'gaming'
db.users.updateOne(
{ name: "Bob Johnson" },
{ $pull: { interests: "gaming" } }
);
常用更新操作符:
$set:设置文档中字段的值。如果字段不存在则创建。$inc:按指定数量增加字段的值。$unset:从文档中移除字段。$push:向数组字段追加一个值。$pull:从数组中移除所有匹配指定查询的值或值的实例。$addToSet:仅当值不存在时才向数组添加值。
3. db.collection.replaceOne()
用新文档替换匹配过滤条件的单个文档。被替换文档的_id字段不能更改。
// 完全替换Bob Johnson的文档
db.users.replaceOne(
{ name: "Bob Johnson" },
{
name: "Robert Johnson",
occupation: "Software Engineer",
status: "active",
email: "[email protected]"
}
);
Upsert选项
updateOne()和updateMany()都支持upsert选项。如果设置为true,并且没有文档匹配过滤条件,MongoDB将根据查询和更新操作插入一个新文档。
db.users.updateOne(
{ name: "David Lee" },
{ $set: { age: 28, city: "Seattle" } },
{ upsert: true } // 如果David Lee不存在,则创建他
);
删除操作
删除操作从集合中移除文档。对删除操作要格外小心,因为它们是不可逆的。
1. db.collection.deleteOne()
删除最多一个匹配指定过滤条件的文档。
// 删除名为'Robert Johnson'的用户(之前是'Bob Johnson')
db.users.deleteOne({ name: "Robert Johnson" });
2. db.collection.deleteMany()
删除所有匹配指定过滤条件的文档。
// 删除所有来自伦敦的用户
db.users.deleteMany({ city: "London" });
警告:要删除集合中的所有文档,请使用空过滤器
{}。此操作无法撤销,请务必谨慎:db.users.deleteMany({}); // 删除'users'集合中的所有文档
3. db.collection.drop()
此方法从数据库中永久移除整个集合,包括其所有文档和索引。
db.users.drop(); // 删除整个'users'集合
警告:删除集合是一种高度破坏性的操作。在执行此命令之前,请确保有适当的备份或绝对确定。
MongoDB CRUD的最佳实践
- 索引:对于频繁查询的字段,创建索引以显著加快读取操作。
db.collection.createIndex({ fieldName: 1 })。 - 投影:只检索你需要的数据。使用投影(
{ field: 1 })可以减少网络带宽和内存使用。 - 批量操作:在插入、更新或删除多个文档时,使用
insertMany()、updateMany()和deleteMany()而不是单个操作,以减少开销。 - 理解操作符:熟悉MongoDB丰富的查询和更新操作符。它们提供了强大的数据操作方式。
- 错误处理:在生产应用程序中,始终为数据库操作实现健壮的错误处理。
- 模式设计:虽然MongoDB是无模式的,但深思熟虑的模式设计对于高效查询和数据一致性至关重要。
在真实数据库中更安全地练习CRUD
MongoDB CRUD的危险之处不在于语法。而是在错误的数据库中运行宽泛的过滤器,尤其是使用updateMany()或deleteMany()时。我喜欢对任何影响现有数据的写入操作采用三步习惯。
首先,将过滤器作为读取运行:
db.users.find(
{ city: "New York", status: "inactive" },
{ name: 1, email: 1, city: 1, status: 1 }
).limit(20);
如果预览返回了你未预期的文档,请立即停止。在考虑更新之前修复过滤器。如果返回空,请确保你处于正确的数据库中(使用db.getName()),并且字段名称与实际文档匹配。
其次,计算匹配文档的数量:
db.users.countDocuments({ city: "New York", status: "inactive" });
这可以让你大致了解影响范围。如果你预期有12个用户,但计数显示12,000,则命令表明出了问题。计数不能替代备份,但它是一个廉价的护栏。
第三,使用最适合工作的最窄命令运行写入。当唯一电子邮件、账户ID或_id应匹配一个文档时,使用updateOne()。仅当有意更改整个段时,才使用updateMany()。
db.users.updateMany(
{ city: "New York", status: "inactive" },
{
$set: {
marketingEmailEnabled: false,
updatedBy: "ops-maintenance-2025-11-04"
}
}
);
添加updatedBy、updatedAt或维护注释并非MongoDB所要求,但它有助于日后有人询问字段为何更改时提供线索。
导致实际错误的常见错误
第一个错误是当你打算更新字段时却替换了文档。这个命令看起来无害,但它会用仅显示的字段替换整个匹配文档:
db.users.updateOne(
{ email: "[email protected]" },
{ city: "Boston" }
);
现代MongoDB期望更新文档使用更新操作符,除非你使用替换样式的方法,因此此模式可能因命令和版本而异而失败。更安全的心智模型很简单:如果你要就地更改字段,请使用$set、$unset、$inc、$push、$pull或其他更新操作符。
db.users.updateOne(
{ email: "[email protected]" },
{ $set: { city: "Boston" } }
);
第二个错误是查询数组时假设顺序总是重要。{ interests: ["reading", "hiking"] }精确匹配数组。{ interests: "reading" }匹配数组包含该值的文档。{ interests: { $all: ["reading", "hiking"] } }匹配包含这两个值的数组,无论顺序如何。这是三个不同的问题。
第三个错误是当过滤器不唯一时,假设findOne()返回“正确的那个”。如果你运行:
db.users.findOne({ city: "New York" });
MongoDB返回一个匹配的文档,但如果没有排序,你不应将该文档视为最新、最旧、最重要或最具代表性。如果顺序重要,请明确说明:
db.users.find({ city: "New York" }).sort({ createdAt: -1 }).limit(1);
第四个错误是直到应用程序变慢才跳过索引。一个包含几千个文档的集合可以隐藏低效的查询。当集合增长时,相同的查询可能会变得痛苦。如果应用程序经常通过email查找用户,请在数据模型允许时创建唯一索引:
db.users.createIndex({ email: 1 }, { unique: true });
这既保护了性能,也保护了数据质量。如果已存在重复电子邮件,命令将失败,这正是你在依赖电子邮件作为标识符之前希望发现的问题。
检查写入操作的实际效果
MongoDB的写入结果值得一读。更新后,查看matchedCount和modifiedCount。matchedCount告诉你有多少文档匹配了过滤器。modifiedCount告诉你实际更改了多少。
如果matchedCount为1且modifiedCount为0,命令可能仍然没问题。也许该字段已经具有请求的值。如果matchedCount为0,则你的过滤器未匹配任何内容。当_id作为字符串而不是ObjectId传递时,这种情况很常见。
db.users.findOne({ _id: ObjectId("6650f1e59d0a41a37c2d8011") });
对于删除,检查deletedCount。如果你预期删除一个文档,但结果显示deletedCount: 0,不要立即运行更广泛的删除。重新检查数据库、集合和过滤器。
当CRUD不够用时
基本的CRUD命令涵盖了大多数日常数据工作,但某些任务需要更强大的工具。如果你要更新多个必须保持一致的集合,请查看副本集或分片集群上的事务。如果你要跨多个文档重塑数据,聚合管道可能比一系列客户端循环更清晰。如果你要迁移生产数据,请使用带有日志记录、试运行模式、备份和回滚计划的脚本。
对于mongosh中的一次性操作,保持命令可读。巧妙的一行代码更难审查,也更难恢复。在生产环境中,无聊的命令通常更好。
一旦你习惯了文档、过滤器和更新操作符,MongoDB CRUD命令就很简单了。在实际工作中重要的技能是深思熟虑:预览过滤器、计算影响、选择最窄的写入命令、读取结果,并留下足够的上下文,以便下一个人能够理解发生了什么变化。