MongoDB 增删改查(CRUD)

任何数据库的入门核心都是增(insert)、查(find)、改(update)、删(delete)四件事——合称 CRUD(Create / Read / Update / Delete)。这一篇我们把 MongoDB 的 CRUD 完整过一遍。学过 JavaScript 的人会觉得格外亲切,因为所有查询条件都是 JS 对象。

1. 准备工作:切换数据库

在 mongosh 里,先用 use 数据库名 切换到一个数据库。不存在的话首次写入会自动创建,不用 CREATE DATABASE。后面所有 db.集合名.操作 的语法,db 就指当前数据库。

2. 增:insertOne / insertMany

insertOne 插一条,insertMany 插多条(传数组)。每个文档不指定 _id 时,MongoDB 自动生成一个 ObjectId

use myblog

// 插入一条:不指定 _id 时自动生成 ObjectId
db.posts.insertOne({
  title: "我的第一篇",
  content: "Hello MongoDB!",
  tags: ["入门", "笔记"],
  views: 0,
  createdAt: new Date()
})
// 返回: { acknowledged: true, insertedId: ObjectId("65a1...") }

// 插入多条:传数组
db.posts.insertMany([
  { title: "第二篇", views: 5,  tags: ["入门"] },
  { title: "第三篇", views: 12, tags: ["进阶"] },
  { title: "第四篇", views: 0,  tags: ["笔记"] }
])
// 返回: { acknowledged: true, insertedCount: 3, insertedIds: {...} }

// 指定 _id(用业务主键,后续再来一次同 _id 会冲突)
db.posts.insertOne({ _id: "p-001", title: "固定ID", views: 0 })

// 插入时是否做有序检查:默认 ordered=true,遇错就停
// 改成 false 则继续插后面的(适合批量导入忽略个别错误)
db.posts.insertMany([...], { ordered: false })

几个易错点:

3. 查:find / findOne

find 返回匹配的游标(可迭代),findOne 只返回第一条。空条件 find() 等价于"查全部"。

// 查询全部(默认一次最多取 20 条,输入 it 看下一批)
db.posts.find()

// 条件查询:字段等于某值
db.posts.find({ views: 0 })
db.posts.find({ title: "第二篇" })

// 嵌套字段用点表示法
db.posts.find({ "author.name": "小明" })

// 数组元素匹配:数组里任一元素等于即可
db.posts.find({ tags: "入门" })

// 只取一条
db.posts.findOne({ views: 0 })

// 投影:只返回需要的字段(1 包含 / 0 排除)
db.posts.find({}, { title: 1, views: 1, _id: 0 })

// 排序:1 升序,-1 降序
db.posts.find().sort({ views: -1 })

// 分页:跳过 5 条,取 5 条(典型的第 2 页,每页 5)
db.posts.find().sort({ _id: 1 }).skip(5).limit(5)

// 计数
db.posts.countDocuments({ views: { $gt: 5 } })

这里的查询条件只是"字段等于某值"的最简单形式——下一篇会专门讲 $gt$in$or查询操作符,能表达复杂条件。

4. 游标与链式调用

find() 返回的不是数组,而是游标(cursor)——一个指向结果的指针。你可以对它链式调用 sort / skip / limit 做排序和分页。

// find() 返回的是"游标(cursor)",可以链式调用
// 顺序建议: sort -> skip -> limit(执行顺序有保障)
db.posts
  .find({ views: { $gte: 5 } })
  .sort({ views: -1 })
  .skip(0)
  .limit(10)

// 把游标转成数组(在驱动里常用,shell 里 find 已自动toArray)
const list = db.posts.find({ tags: "入门" }).toArray()
print("数量: " + list.length)

// 用 forEach 遍历
db.posts.find().forEach(p => {
  print(p.title + " - " + p.views)
})

// 字段是否有不同值:distinct
db.posts.distinct("tags")
// 输出: ["入门", "笔记", "进阶"]

关于分页有个性能坑:skip 在大偏移量下很慢(要扫过前 N 条才能定位)。生产环境翻深页时通常改用游标分页(记住上一页最后一条的 _id,下次查 _id 大于它的)。

5. 改:updateOne / updateMany / replaceOne

更新是最容易踩坑的 CRUD 操作。关键分清两件事:updateOne 默认会用你传的对象整体替换原文档——通常不是你想要的;要做到"只改某几个字段",必须用 $set 等更新操作符。

// 改一条:updateOne(查询条件, 更新操作)
// 第一个参数定位要改哪些文档,第二个参数说怎么改
db.posts.updateOne(
  { title: "我的第一篇" },
  { $set: { views: 100 } }       // $set 只改指定字段,其他不动
)

// 改多条:updateMany,所有匹配的都改
db.posts.updateMany(
  { views: { $lt: 10 } },
  { $set: { views: 10 } }
)

// 整体替换:replaceOne(不用 $set,直接换文档内容,_id 保留)
db.posts.replaceOne(
  { title: "第二篇" },
  { title: "第二篇(改)", views: 99, note: "整体替换" }
)

// upsert:找不到就插入(关键词 $set 配合 upsert: true)
db.posts.updateOne(
  { title: "不存在的文章" },
  { $set: { views: 0, createdAt: new Date() } },
  { upsert: true }
)

// 返回更新前后的文档:findOneAndUpdate
db.posts.findOneAndUpdate(
  { title: "我的第一篇" },
  { $inc: { views: 1 } },
  { returnNewDocument: true }
)

常用更新操作符简介(下一篇详讲):$set(设值)、$unset(删字段)、$inc(自增)、$push / $pull(数组增删)。这里先记住一条铁律:做部分修改必须用 $ 开头的更新操作符

upsert: true 是个高频技巧:找不到就插入,适合做"幂等写入"——比如记录每日访问数,有就更新、没有就建。

6. 删:deleteOne / deleteMany

删除语法简单,但最危险。生产环境删数据前一定要先 find 一遍确认条件。

// 删一条
db.posts.deleteOne({ title: "第四篇" })

// 删多条:小心!没条件就是全删
db.posts.deleteMany({ views: 0 })
db.posts.deleteMany({})                  // 清空整个集合(谨慎)

// 删之前先 find 一遍确认,避免误删
db.posts.find({ views: 0 })              // 先看一眼
db.posts.deleteMany({ views: 0 })        // 确认无误再删

// 找到并删除一条(原子操作)
db.posts.findOneAndDelete({ title: "第三篇" })

// 删除整个集合(连索引一起删)
db.posts.drop()

// 删除整个数据库(更危险)
db.dropDatabase()

删整库(db.dropDatabase())和清空集合(db.col.deleteMany({}))都是不可逆操作(除非开了 oplog 或备份)。生产环境建议先做软删除(加 deletedAt 字段),不要真删。

7. 实战:批量造数据 + 统计

mongosh 是完整 JS 环境,可以写 for 循环批量造测试数据,非常适合学习阶段。

// mongosh 里批量造测试数据(用 JS 循环)
for (let i = 0; i < 100; i++) {
  db.posts.insertOne({
    title: "文章 " + i,
    views: Math.floor(Math.random() * 1000),
    tags: i % 2 === 0 ? ["入门"] : ["进阶", "笔记"],
    createdAt: new Date()
  })
}

// 批量更新也很常用:把所有 views < 10 的提到 10
db.posts.updateMany(
  { views: { $lt: 10 } },
  { $set: { views: 10 } }
)
print(db.posts.countDocuments())  // 看看总数

8. CRUD 方法一览表

小结

这一篇覆盖了 MongoDB 全部基础 CRUD:增用 insert、查用 find(配 sort/skip/limit)、改用 update(务必带 $set)、删用 delete(谨慎)。但 find 的查询条件目前还只会"等于"——下一篇我们系统讲 $gt$in$or查询操作符,让 find 真正强大起来。

← 上一篇 MongoDB 环境安装

下一篇 MongoDB 查询操作符

✈️💬