3. MongoDB快速上手
MongoDB 的“快”来自它把数据组织成文档,而不是先拆成行列再拼装。理解文档模型、BSON 类型、数据库与集合的层级关系,再通过 mongosh 完成一次完整的增删改查,就能把抽象概念落到可操作的对象上。本节以“课程选课记录”为示例场景,所有数据均为教学示例,不表示真实系统实测结果。
一、文档模型:一条记录就是一个自包含对象
MongoDB 中最小的数据单位是文档(Document),它使用类似 JSON 的键值结构,但实际存储格式是 BSON。一个文档可以嵌套子文档和数组,因此一条“学生选课”记录可以同时包含学生信息、所选课程列表和状态,而不需要像关系型数据库那样拆成多张表再关联。
文档模型的关键约束是:每个文档必须有 _id 字段,若插入时未提供,驱动或 mongosh 会自动生成一个 ObjectId。字段名区分大小写,同一个文档内字段名不能重复。文档大小上限为 16MB,超过时应考虑 GridFS 或拆分结构。
二、BSON:JSON 的二进制扩展
BSON(Binary JSON)在 JSON 基础上增加了更多数据类型,使 MongoDB 能精确表达时间、二进制、整数宽度和有序字段。常见类型包括:
String:UTF-8 字符串,如"name": "林晓"。Int32/Int64/Double:整数与浮点数,mongosh 中默认数字可能被识别为 Double,需要显式使用NumberInt()或NumberLong()。Date:毫秒级时间戳,如ISODate("2025-03-01T08:00:00Z")。ObjectId:12 字节唯一标识,常作为主键。Array:有序列表,如["MongoDB", "微服务"]。Embedded Document:嵌套子文档,如{ "contact": { "email": "..." } }。Null、Boolean、Binary、Decimal128等。
BSON 的类型选择会直接影响查询和聚合结果。例如,用字符串存日期会导致范围查询失效;用 Double 存金额可能产生精度误差,金融场景应使用 Decimal128。
三、数据库、集合与文档的层级
MongoDB 的层级为:数据库(Database)→ 集合(Collection)→ 文档(Document)。一个 mongod 实例可承载多个数据库,每个数据库包含多个集合,集合中存放文档。集合不强制固定结构,但同一集合内文档结构相近时查询效率更高。
常用命令:show dbs 查看数据库;use course_db 切换或创建数据库;db.createCollection("enrollments") 显式创建集合;show collections 查看当前库集合。注意:use 一个不存在的库不会立即创建,直到写入第一条数据。
四、mongosh 连接与基础操作
mongosh 是 MongoDB 官方 Shell,支持 JavaScript 语法。启动本地实例:
mongosh "mongodb://127.0.0.1:27017"
连接后先确认当前数据库:db。切换到课程库并插入一条选课记录:
use course_db
db.enrollments.insertOne({
name: "林晓",
age: NumberInt(21),
courses: ["MongoDB", "微服务架构"],
contact: { email: "linxiao@example.com", city: "杭州" },
status: "active",
createdAt: new Date()
})
返回结果中会包含 acknowledged: true 和自动生成的 insertedId。若插入多条,可用 insertMany([...]),返回 insertedIds 数组。
五、查询:条件、投影与排序
查询全部文档:db.enrollments.find()。只返回部分字段(投影):
db.enrollments.find(
{ status: "active" },
{ name: 1, courses: 1, _id: 0 }
)
常用查询操作符:$gt、$lt、$in、$exists。例如查询年龄大于 20 且城市为杭州的记录:
db.enrollments.find({
age: { $gt: 20 },
"contact.city": "杭州"
}).sort({ createdAt: -1 }).limit(5)
注意嵌套字段要用点号路径 "contact.city"。排序 -1 表示降序,1 表示升序。若结果较多,可用 it 继续翻页。
六、更新:$set 与 $push 的差异
更新单条记录,把状态改为 completed:
db.enrollments.updateOne(
{ name: "林晓" },
{ $set: { status: "completed", updatedAt: new Date() } }
)
向数组追加一门课程:
db.enrollments.updateOne(
{ name: "林晓" },
{ $push: { courses: "数据建模" } }
)
$set 用于修改或新增字段,$push 用于向数组追加元素。若字段不存在,$set 会创建它;若数组不存在,$push 也会创建数组。更新多条使用 updateMany(),返回 matchedCount 与 modifiedCount,可据此判断是否命中。
七、删除与结果判断
删除单条:db.enrollments.deleteOne({ name: "林晓" });删除多条:db.enrollments.deleteMany({ status: "completed" })。返回 deletedCount 表示实际删除数量。若为 0,说明条件未命中,应检查字段名、大小写或值类型是否匹配。
db.enrollments.find().pretty() 可格式化输出;db.enrollments.countDocuments({ status: "active" }) 可快速统计。生产环境执行更新或删除前,建议先用相同条件执行 find() 确认目标文档。八、一次完整 CRUD 闭环示例
下面按“插入 → 查询 → 更新 → 验证 → 删除”的顺序完成一次闭环,所有数据为教学示例:
// 1. 插入
db.enrollments.insertOne({
name: "陈默",
age: NumberInt(23),
courses: ["MongoDB"],
status: "active",
createdAt: new Date()
})
// 2. 查询确认
db.enrollments.find({ name: "陈默" }, { name: 1, courses: 1, status: 1 })
// 3. 更新:追加课程并改状态
db.enrollments.updateOne(
{ name: "陈默" },
{ $push: { courses: "微服务数据架构" }, $set: { status: "completed" } }
)
// 4. 验证更新结果
db.enrollments.findOne({ name: "陈默" })
// 5. 删除并确认
db.enrollments.deleteOne({ name: "陈默" })
db.enrollments.countDocuments({ name: "陈默" }) // 期望返回 0
判断闭环是否成功,关键看三点:插入返回的 insertedId 是否存在;更新返回的 modifiedCount 是否为 1;删除后 countDocuments 是否为 0。若某一步不符合预期,优先检查查询条件中的字段路径、BSON 类型和大小写,而不是直接怀疑数据库故障。
updateOne 时,若替换文档整体而非使用更新操作符,容易丢失未列出的字段。除明确需要替换外,应始终使用 $set、$push、$unset 等操作符进行局部更新。掌握文档模型与 BSON 类型,理解数据库、集合、文档的层级,再通过 mongosh 完成插入、查询、更新、删除与结果验证,就具备了继续学习索引、聚合和微服务数据访问层的基础操作能力。