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 })几个易错点:
- 主键冲突:同
_id再插一次会报E11000 duplicate key错误。 - ordered:默认
ordered: true,批量插入遇到一条出错就停止;改成false会继续插后面的(适合批量导入忽略个别错误)。 - 写确认:默认
acknowledged: true表示写入已确认;批量导入追求速度可设writeConcern: { w: 0 }(不等确认就返回,但有丢数据风险)。
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 方法一览表
- 增:
insertOne(一条)、insertMany(多条)。 - 查:
find(游标)、findOne(一条)、distinct(去重值)、countDocuments(计数)。 - 改:
updateOne、updateMany、replaceOne、findOneAndUpdate(原子找改)。 - 删:
deleteOne、deleteMany、findOneAndDelete、drop(删集合)。
小结
这一篇覆盖了 MongoDB 全部基础 CRUD:增用 insert、查用 find(配 sort/skip/limit)、改用 update(务必带 $set)、删用 delete(谨慎)。但 find 的查询条件目前还只会"等于"——下一篇我们系统讲 $gt、$in、$or 等查询操作符,让 find 真正强大起来。
← 上一篇 MongoDB 环境安装
下一篇 MongoDB 查询操作符 →