第三章:数据模型与文档结构
最后更新: 2024-01-01
作者: MongoDB Team
页面目录
第三章:数据模型与文档结构
理解 MongoDB 的文档模型和 Schema 设计原则
3.1 文档结构
基本文档
// 简单的用户文档
{
_id: ObjectId("507f1f77bcf86cd799439011"),
username: "john_doe",
email: "john@example.com",
age: 28,
is_active: true,
created_at: ISODate("2024-01-01")
}
嵌套文档
// 包含嵌套对象的文档
{
_id: ObjectId("507f1f77bcf86cd799439012"),
username: "alice",
profile: {
first_name: "Alice",
last_name: "Smith",
address: {
street: "123 Main St",
city: "Beijing",
country: "China",
zip: "100000"
},
phone: {
mobile: "13800138000",
home: "010-12345678"
}
},
preferences: {
language: "zh-CN",
timezone: "Asia/Shanghai"
}
}
数组文档
// 包含数组的文档
{
_id: ObjectId("507f1f77bcf86cd799439013"),
username: "bob",
tags: ["mongodb", "database", "nosql", "javascript"],
scores: [
{ subject: "Math", score: 95 },
{ subject: "English", score: 88 },
{ subject: "Science", score: 92 }
],
skills: [
{ name: "JavaScript", level: "expert" },
{ name: "Python", level: "advanced" }
]
}
3.2 _id 字段和 ObjectId
ObjectId 结构
ObjectId 是 MongoDB 默认的主键类型,12 字节组成:
┌─────────────────────────────────────────────────────────┐
│ ObjectId 结构 │
├──────────┬──────────┬──────────┬──────────┬────────────┤
│ 时间戳 │ 机器ID │ 进程ID │ 计数器 │ │
│ 4 字节 │ 3 字节 │ 2 字节 │ 3 字节 │ 共12字节 │
└──────────┴──────────┴──────────┴──────────┴────────────┘
ObjectId 操作
// 创建 ObjectId
ObjectId()
ObjectId("507f1f77bcf86cd799439011")
// 获取 ObjectId 的时间戳
ObjectId("507f1f77bcf86cd799439011").getTimestamp()
// ISODate("2024-01-01T00:00:00Z")
// 比较 ObjectId
ObjectId("507f1f77bcf86cd799439011") < ObjectId("507f1f77bcf86cd799439012")
// 自定义 _id
db.users.insertOne({
_id: "user_001",
username: "custom_id_user"
})
3.3 Schema 设计原则
嵌入 vs 引用
┌─────────────────────────────────────────────────────────────┐
│ 数据模型设计策略 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 嵌入 (Embedded) 引用 (Reference) │
│ ───────────── ────────────── │
│ ✓ 数据相关性高 ✓ 数据独立性强 │
│ ✓ 读多写少 ✓ 经常单独访问 │
│ ✓ 需要原子更新 ✓ 数据重复开销大 │
│ ✓ 文档大小有限制 ✓ 需要 JOIN 操作 │
│ │
└─────────────────────────────────────────────────────────────┘
嵌入文档场景
// ✅ 推荐嵌入:用户地址信息
{
_id: ObjectId(),
username: "user1",
address: {
street: "...",
city: "...",
country: "..."
}
}
// ✅ 推荐嵌入:博客文章和评论(评论通常一起读取)
{
_id: ObjectId(),
title: "MongoDB 教程",
content: "...",
comments: [
{ author: "Alice", text: "很棒的文章" },
{ author: "Bob", text: "学到了很多" }
]
}
引用文档场景
// ✅ 推荐引用:博客文章和用户(用户被多个文章引用)
// 用户集合
{
_id: ObjectId(),
username: "alice",
email: "alice@example.com"
}
// 文章集合(引用用户)
{
_id: ObjectId(),
title: "MongoDB 教程",
author_id: ObjectId("..."), // 引用用户
content: "..."
}
3.4 文档验证
创建验证规则
// 为集合添加文档验证
db.createCollection("users", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["username", "email"],
properties: {
username: {
bsonType: "string",
minLength: 3,
maxLength: 50,
description: "用户名必须为3-50个字符的字符串"
},
email: {
bsonType: "string",
pattern: "^.+@.+\\..+$",
description: "必须是有效的邮箱地址"
},
age: {
bsonType: "number",
minimum: 0,
maximum: 150
},
status: {
enum: ["active", "inactive", "suspended"]
}
}
}
},
validationLevel: "moderate",
validationAction: "warn" // warn 或 error
})
// 查看集合的验证规则
db.getCollectionInfos({ name: "users" })
// 修改验证规则
db.runCommand({
collMod: "users",
validator: { ... },
validationAction: "error"
})
3.5 集合设计最佳实践
BSON 文档大小限制
| MongoDB 版本 | 文档大小限制 |
|---|---|
| 4.4+ | 16 MB |
| 2.6 - 4.2 | 16 MB |
| 2.4 及以前 | 4 MB |
命名规范
// 集合命名
// ✅ 正确
db.getCollection("users")
db.getCollection("order_items")
db.getCollection("system_events")
// ❌ 避免
db.getCollection("my collection") // 含空格
db.getCollection("my.collection") // 含点号
db.getCollection("") // 空名称
// 数据库命名
use my_app
use analytics_platform
use reporting_system
字段命名规范
// ✅ 推荐:使用小写下划线
{
user_id: ObjectId(),
first_name: "John",
last_name: "Doe",
created_at: ISODate(),
updated_at: ISODate(),
is_active: true
}
// 避免使用以 $ 开头的字段名
// 避免使用含 . 的字段名(虽然支持但容易混淆)
3.6 灵活 Schema vs 固定 Schema
灵活 Schema
MongoDB 允许同一集合中的文档有不同结构:
// 集合中可以存储不同结构的文档
db.products.insertMany([
{ name: "Book", price: 29.99, author: "John" },
{ name: "T-Shirt", price: 19.99, size: "M", color: "blue" },
{ name: "Laptop", price: 999.99, specs: { cpu: "Intel i7", ram: "16GB" } }
])
使用 Schema 库(Mongoose 风格)
虽然 MongoDB 原生不强制 Schema,但可以使用验证器:
// 定义 Schema(使用验证器)
const userSchema = {
$jsonSchema: {
bsonType: "object",
required: ["username", "email", "password"],
properties: {
username: { bsonType: "string", minLength: 3 },
email: { bsonType: "string" },
password: { bsonType: "string" },
profile: {
bsonType: "object",
properties: {
firstName: { bsonType: "string" },
lastName: { bsonType: "string" }
}
}
}
}
}
db.createCollection("users", { validator: userSchema })
3.7 常见数据模型示例
博客系统
// 用户
{
_id: ObjectId(),
username: "alice",
email: "alice@example.com",
password_hash: "...",
profile: {
avatar: "...",
bio: "Software Engineer",
social_links: {
github: "...",
twitter: "..."
}
},
created_at: ISODate()
}
// 文章
{
_id: ObjectId(),
author_id: ObjectId(),
title: "MongoDB 最佳实践",
slug: "mongodb-best-practices",
content: "...",
tags: ["mongodb", "database", "nosql"],
status: "published",
view_count: 1000,
published_at: ISODate(),
updated_at: ISODate()
}
// 评论
{
_id: ObjectId(),
post_id: ObjectId(),
author_id: ObjectId(),
parent_id: null, // 用于嵌套评论
content: "很棒的文章!",
created_at: ISODate()
}
电商系统
// 商品
{
_id: ObjectId(),
name: "iPhone 15 Pro",
category: "Electronics",
subcategory: "Mobile Phones",
price: NumberDecimal("7999.00"),
stock: 100,
attributes: {
color: ["Black", "White", "Blue"],
storage: ["128GB", "256GB", "512GB"],
weight: "187g"
},
images: [
{ url: "...", is_primary: true },
{ url: "...", is_primary: false }
],
reviews: [
{
user_id: ObjectId(),
rating: 5,
comment: "非常好用!",
helpful_count: 10
}
]
}
// 订单
{
_id: ObjectId(),
order_number: "ORD202401010001",
user_id: ObjectId(),
items: [
{
product_id: ObjectId(),
name: "iPhone 15 Pro",
quantity: 1,
price: NumberDecimal("7999.00"),
subtotal: NumberDecimal("7999.00")
}
],
shipping_address: {
recipient: "张三",
phone: "13800138000",
address: {
province: "北京",
city: "北京市",
district: "朝阳区",
detail: "某街道某号"
}
},
total_amount: NumberDecimal("7999.00"),
status: "shipped",
created_at: ISODate()
}
💡 实践提示
- 避免文档过大:16MB 限制听起来很大,但设计时应避免存储大对象
- 合理使用嵌套:嵌套深度建议不超过 2-3 层
- 考虑读写模式:读多写少适合嵌入,写多读少或需要原子更新时考虑引用
- 预留扩展字段:为未来可能添加的字段预留位置
📚 继续学习