正如我们之前提到的,在 MongoDB 中,数据以 BSON 文档的形式存储,类似于结构化的 JSON 对象。高效、标准化地进行文档的插入、更新和删除操作,是确保数据一致性与系统可维护性的基础。 上一堂课程我们简单介绍了 MongoDB 文档级别的核心操作方法,现在我们将继续深入学习 MongoDB 文档操作的更多技巧,帮助你更好地管理你的数据。

在实际应用中,针对如在线书店等场景,每当有新书到货时,通常需要将书籍的元数据信息持久化存储于数据库。MongoDB 推荐使用 insertOne 方法进行单条文档的专业插入操作。该方法不仅性能优异,还能确保数据安全可靠地写入指定集合,满足业务对数据一致性与可追溯性的要求。
|db.bookstore.insertOne({"title": "深入浅出 Node.js", "author": "朴灵", "price": 89})
当你执行这个命令时,MongoDB 会自动为这个文档生成一个独特的 _id 字段,就像每本书都有自己独特的ISBN编号一样。这个字段确保了每个文档在集合中的唯一性,即使你忘记手动指定标识符也不用担心。
在实际工作中,我们经常需要同时录入多条数据。比如一家餐厅要将整个菜单录入系统,如果一道菜一道菜地录入,不仅耗时还容易出错。这时候 insertMany 方法就派上用场了,它允许我们一次性插入多个文档。
|db.restaurant.insertMany([ {"name": "宫保鸡丁", "category": "川菜", "price": 35}, {"name": "麻婆豆腐", "category": "川菜", "price": 25}, {"name": "糖醋排骨", "category": "江浙菜", "price": 45} ])
批量插入显著提升了操作效率,原因在于它有效地降低了客户端与数据库之间的交互次数,从而优化了整体数据写入性能。
在使用 insertMany 时,你可以选择是否保持插入顺序。默认情况下,MongoDB 会按照你提供的数组顺序依次插入文档。但如果某个文档插入失败(比如违反了唯一性约束),后续的文档就不会被插入了。
|db.products.insertMany([ {"_id": 1, "name": "笔记本电脑"}, {"_id": 2, "name": "无线鼠标"}, {"_id": 2, "name": "机械键盘"}, // 这里会出错,因为 _id 重复 {"_id": 3, "name": "显示器"} ], {"ordered": false})
通过设置 {"ordered": false} 参数,MongoDB 会在批量插入过程中遇到某条文档插入失败时,自动跳过该文档并继续插入后续文档,而不会中断整个批处理。这种方式可最大化数据写入效率,确保所有未违反约束条件的文档均能成功插入。
MongoDB 对插入的数据会执行基础的结构和约束校验,其中最重要的一点是单个文档不能超过 16MB。这一限制主要是为了保障数据库的性能和可靠性。 其实对于大多数应用场景来说,16MB 已经非常充裕。例如,常见一张高分辨率医学影像(如MRI影像)通常只需几兆字节,单个病人的检查报告和图像合并后也常常远小于该限制。
如果你需要评估某个文档具体占用的空间,可在 MongoDB shell 中使用 Object.bsonsize() 函数:
|var myDoc = {"title": "MongoDB 实战指南", "content": "...很长的内容..."} Object.bsonsize(myDoc) #输出 1024 // 返回字节数

在实际数据库管理过程中,经常需要根据精确条件删除指定文档。deleteOne 方法能够高效且精准地移除与筛选条件匹配的首个文档。
例如,在员工信息管理系统中,需要删除符合“已离职”状态的员工记录:
|db.employees.deleteOne({"employeeId": "E2023001", "status": "离职"}) #输出 {'acknowledged': true, 'deletedCount': 1}
此操作会根据指定筛选条件,精确删除首个匹配的文档。如果存在多条符合条件的文档,只有第一条记录会被移除,其余文档保持不变,确保删除过程具备可控性和高精度。
当需要同时移除大量不再需要的数据时,可使用 deleteMany 方法高效地批量删除。例如,电商平台批量清理所有已过期的优惠券记录:
|db.coupons.deleteMany({"expiryDate": {"$lt": new Date("2023-12-31")}}) #输出 {'acknowledged': true, 'deletedCount': 156}
这个操作会删除所有在2023年12月31日之前过期的优惠券记录。返回的 deletedCount 告诉我们具体删除了多少条记录,让我们对操作结果一目了然。
在需要清空整个集合内的所有文档时,虽然可以使用 deleteMany({}),但推荐采用更高效的 drop() 方法,直接移除整个集合对象:
|db.tempData.drop() #输出 true
相比逐条删除文档,直接删除整个集合(drop)更加高效。需要注意的是,删除集合后,若需恢复同样的结构,请及时重新创建必要的索引,以保证后续操作的性能和数据完整性。

在实际开发中,若需对文档的结构进行整体性调整,可采用 replaceOne 方法实现文档的完全替换。该方法会根据筛选条件用新的文档内容覆盖原有文档,仅保留 _id 字段不变,其余全部采用新结构。
例如,将用户信息文档从扁平结构重构为嵌套结构:
|// 原始文档结构 { "_id": ObjectId("..."), "name": "张三", "friends": 50, "enemies": 2 } // 重构后的结构 var user = db.users.findOne({"name": "张三"}) var newUser = { "_id": user._id, "username": user.name, "relationships"
使用 replaceOne 时务必确保过滤条件只能匹配到一个文档,否则可能会因为 _id 重复而导致操作失败。
在实际开发中,往往只需对文档的部分字段进行修改,而无需整体替换文档结构。此时可借助 MongoDB 提供的各类更新操作符,针对指定字段精确、高效地完成局部更新。
$set 是最常用的更新操作符,用于为文档指定的字段赋值。若目标字段不存在,则自动创建对应字段:
|db.users.updateOne( {"username": "张三"}, {"$set": {"email": "zhangsan@example.com", "lastLogin": new Date()}} )
此操作可用于为用户文档增加新的属性字段,或对现有字段数据进行更新,增强信息的完整性与准确性。
在实际业务场景中,经常需要对数值类型字段进行累加或递减。例如,实现页面访问量统计、用户积分增长等需求:
|db.articles.updateOne( {"title": "MongoDB 学习指南"}, {"$inc": {"viewCount": 1, "likeCount": 5}} )
以上操作会将目标文章的 viewCount 字段自增 1,likeCount 字段自增 5。若相关字段在原文档中不存在,MongoDB 会自动创建并赋予指定的初始增量,有效防止缺失字段带来的异常。
针对数组类型字段,MongoDB 提供了一套高效且灵活的操作符,以支持复杂的数组元素管理要求。
例如,利用 $push 操作符可向指定的数组字段附加新元素:
|db.users.updateOne( {"username": "李四"}, {"$push": {"hobbies": "摄影"}} )
如果需要确保数组中不出现重复元素,可以使用 $addToSet:
|db.users.updateOne( {"username": "李四"}, {"$addToSet": {"skills": {"$each": ["JavaScript", "Python", "MongoDB"]}}} )
要从数组中移除元素,可以使用 $pull:
|> db.users.updateOne( {"username": "李四"}, {"$pull": {"hobbies": "游戏"}} )
在实际开发中,针对数组字段中某一特定元素的精确更新,通常可借助位置操作符 $ 或数组过滤器完成。这些机制允许我们根据条件精准锁定并修改数组中的目标元素,满足高度定制化的数据维护需求。
例如,在一个博客评论系统中,若需精确更新某条评论的内容,可以如下操作:
|db.posts.updateOne( {"comments.author": "王五"}, {"$set": {"comments.$.content": "这是更新后的评论内容"}} )
对于更复杂的数组更新需求,可以使用数组过滤器:
|db.posts.updateOne( {"_id": ObjectId("...")}, {"$set": {"comments.$[elem].hidden": true}}, {"arrayFilters": [{"elem.votes": {"$lte": -5}}]} )
这个操作会将所有投票数小于等于-5的评论标记为隐藏。
upsert 是 "update" 和 "insert" 的组合词,它提供了一种智能的数据处理方式:如果找到匹配的文档就更新,找不到就插入新文档。
这在很多场景下非常实用,比如网站访问统计:
|> db.pageViews.updateOne( {"url": "/products"}, {"$inc": {"count": 1}}, {"upsert": true} )
如果 "/products" 页面的记录已存在,就将访问次数加1;如果不存在,就创建一个新记录并设置访问次数为1。这种方式避免了先查询再决定是插入还是更新的复杂逻辑。
当需要更新多个符合条件的文档时,updateMany 方法能够一次性处理所有匹配的记录:
|db.products.updateMany( {"category": "电子产品"}, {"$mul": {"price": 0.9}} // 所有电子产品打9折 )
这个操作会将所有电子产品的价格乘以0.9,实现批量打折的效果。
MongoDB 保证对单个文档的更新操作具有原子性,即每一次更新要么全部生效,要么完全不生效,绝不存在数据部分变更导致的不一致状态。这一特性确保了在数据库事务过程中,单个文档的完整性与一致性,防止因中断或并发操作导致的数据异常。
在实际生产环境中,经常存在在更新文档的同时需要获取其更新前或更新后内容的需求。MongoDB 提供的 findOneAndUpdate 方法可实现原子性地查找并更新文档,并根据参数选择返回更新前或更新后的文档。这对于需要在并发条件下同步处理任务的业务场景至关重要。
例如,在任务队列系统中,多个工作进程需要安全地检索待处理任务并将其状态原子性地更新为“处理中”:
|> var task = db.taskQueue.findOneAndUpdate( {"status": "waiting"}, {"$set": {"status": "processing", "assignedTo": "worker-01"}}, {"sort": {"priority": -1}, "returnNewDocument": true} )
这个操作会找到优先级最高的等待任务,将其状态更新为“处理中”,并返回更新后的文档。整个过程是原子性的,避免了多个工作进程抢夺同一个任务的竞争条件。
在实际应用中,某些字段仅需在文档首次插入时初始化,而不应在后续的更新操作中被修改。此时可以利用 $setOnInsert 操作符,使这些字段仅在 upsert 新建文档时被赋值,确保数据的一致性与可追溯性:
|db.users.updateOne( {"email": "user@example.com"}, { "$set": {"lastLogin": new Date()}, "$setOnInsert": {"createdAt": new Date(), "registrationSource": "web"} }, {"upsert": true} )
如果用户记录已存在,只会更新 lastLogin 字段;如果是新用户,还会设置 createdAt 和 registrationSource 字段。
针对不同的业务场景,应根据操作类型合理选择相应的 MongoDB 方法,以充分发挥数据库性能与数据一致性:
假设我们正在管理一个小型图书馆的数据库。当前数据库中没有任何图书记录。现在我们需要添加第一本书的信息。
原始数据: 数据库名为 library,集合名为 books。
操作要求: 使用 insertOne 方法向 books 集合中添加一本名为《JavaScript高级程序设计》的图书,作者为Nicholas C. Zakas,价格为99元。
|use library db.books.insertOne({ "title": "JavaScript高级程序设计", "author": "Nicholas C. Zakas", "price": 99 })
学校新学期开始,需要将新入学的学生信息录入系统。需要一次性录入3名学生的信息。
原始数据: 数据库名为 school,集合名为 students。
操作要求: 使用 insertMany 方法向 students 集合中批量插入以下3名学生信息:
|use school db.students.insertMany([ {"name": "小明", "age": 15, "grade": "高中一年级"}, {"name": "小红", "age": 14, "grade": "初中三年级"}, {"name": "小刚", "age": 16, "grade": "高中二年级"} ])
电商平台需要清理过期商品。当前有一件商品已经过期,需要将其从数据库中删除。
原始数据: 数据库名为 shop,集合名为 products。集合中已存在以下商品:
|{ "_id": ObjectId("507f1f77bcf86cd799439011"), "name": "牛奶", "category": "食品", "expiryDate": ISODate("2023-12-01T00:00:00Z"), "price": 5.5 }
操作要求: 使用 deleteOne 方法删除名称为"牛奶"的商品记录。
|use shop db.products.deleteOne({"name": "牛奶"})
图书馆的图书价格需要调整。一本书的价格从原来的89元调整为95元。
原始数据: 数据库名为 library,集合名为 books。集合中已存在以下图书:
|{ "_id": ObjectId("507f1f77bcf86cd799439011"), "title": "深入浅出Node.js", "author": "朴灵", "price": 89 }
操作要求: 使用 updateOne 方法和 $set 操作符,将《深入浅出Node.js》一书的price字段更新为95。
|use library db.books.updateOne( {"title": "深入浅出Node.js"}, {"$set": {"price": 95}} )
社交平台需要为用户添加新的兴趣标签。一个用户想要添加"阅读"作为新的兴趣爱好。
原始数据: 数据库名为 social,集合名为 users。集合中已存在以下用户信息:
|{ "_id": ObjectId("507f1f77bcf86cd799439011"), "username": "张三", "age": 25, "hobbies": ["音乐", "运动"] }
操作要求: 使用 updateOne 方法和 $push 操作符,为用户"张三"添加"阅读"兴趣爱好。
|use social db.users.updateOne( {"username": "张三"}, {"$push": {"hobbies": "阅读"}} )
网站需要统计各页面的访问量。当用户访问一个页面时,需要增加该页面的访问计数。
原始数据: 数据库名为 website,集合名为 pageviews。集合中可能有也可能没有 /about 页面的访问记录。
操作要求: 使用 updateOne 方法配合 upsert 参数,实现 /about 页面的访问量+1。如果该页面记录不存在,则创建新记录并设置初始访问量为1。
|use website db.pageviews.updateOne( {"page": "/about"}, {"$inc": {"views": 1}}, {"upsert": true} )
学校需要更新学生的考试成绩。一名学生的数学成绩需要从85分增加到90分。
原始数据: 数据库名为 school,集合名为 students。集合中已存在以下学生信息:
|{ "_id": ObjectId("507f1f77bcf86cd799439011"), "name": "小明", "scores": { "math": 85, "english": 92, "chinese": 88 } }
操作要求: 使用 updateOne 方法和 $set 操作符,将小明的数学成绩从85分更新为90分。
|use school db.students.updateOne( {"name": "小明"}, {"$set": {"scores.math": 90}} )