Appearance
1.3 数据模型与文档结构
1. 概述
MongoDB的数据模型基于文档和集合,与关系型数据库的表和行有本质区别。理解MongoDB的数据模型和文档结构对于设计高效的数据库应用至关重要。本章节将详细介绍MongoDB的数据模型设计原则、文档结构特点以及最佳实践。
2. 基本概念
2.1 文档结构
文档结构是指MongoDB中文档的组织方式和字段布局。MongoDB文档使用BSON格式,支持嵌套结构和数组,具有很高的灵活性。
语法:文档使用键值对表示,键是字符串,值可以是各种BSON类型
语义:文档结构决定了数据的存储方式和查询效率
规范:
- 文档大小限制为16MB
- 键名不能包含空字符和点号
- 建议使用有意义的键名
2.2 嵌套文档
嵌套文档是指文档中包含其他文档作为字段值。嵌套文档可以表示复杂的数据关系,减少关联查询的需求。
语法:使用对象表示嵌套文档,可以通过点号访问嵌套字段
语义:嵌套文档表示一对多或一对一的关系
规范:
- 嵌套深度不宜过深(建议不超过3层)
- 嵌套文档不宜过大
- 合理使用嵌套和引用
2.3 数组字段
数组字段是指文档中包含数组作为字段值。数组可以存储多个值,支持索引和查询操作。
语法:使用方括号表示数组,支持各种数据类型
语义:数组表示一对多的关系或列表数据
规范:
- 数组元素不宜过多(建议不超过1000个)
- 避免在数组中存储过大的文档
- 合理使用数组索引
2.4 引用关系
引用关系是指通过存储其他文档的_id来建立文档之间的关联。引用关系类似于关系型数据库的外键。
语法:存储引用文档的_id值
语义:引用关系表示文档之间的关联
规范:
- 引用字段命名建议使用_id后缀
- 确保引用的文档存在
- 考虑使用$lookup进行关联查询
3. 原理深度解析
3.1 文档存储原理
MongoDB使用BSON格式存储文档,BSON是JSON的二进制编码形式。BSON文档在存储时会进行压缩,以减少存储空间占用。文档在磁盘上以BSON格式存储,在内存中可以转换为PHP对象。
3.2 文档更新机制
MongoDB的文档更新是原子的,整个文档会被替换或部分更新。对于大型文档,部分更新可以提高性能。MongoDB使用$set、$unset等操作符实现部分更新。
3.3 文档查询原理
MongoDB的查询基于BSON文档匹配,支持嵌套字段查询和数组查询。查询时会使用索引加速查找,如果没有合适的索引,会进行全表扫描。
4. 常见错误与踩坑点
4.1 错误1:过度嵌套
错误表现:文档嵌套层级过深,导致查询和维护困难
产生原因:为了减少关联查询,过度使用嵌套文档
解决方案:合理控制嵌套深度,使用引用关系替代过深的嵌套
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->test->documents;
$overNested = [
'level1' => [
'level2' => [
'level3' => [
'level4' => [
'level5' => '数据'
]
]
]
]
];
$result = $collection->insertOne($overNested);
echo "插入过度嵌套的文档\n";
echo "错误:嵌套层级过深(5层)\n";
echo "建议:使用引用关系替代过深嵌套\n";
$optimized = [
'level1' => [
'level2' => [
'level3' => '数据'
]
],
'related_id' => new MongoDB\BSON\ObjectId()
];
$collection->insertOne($optimized);
echo "优化后:嵌套层级控制在3层以内\n";
echo "运行结果: 演示过度嵌套问题\n";
?>运行结果:
插入过度嵌套的文档
错误:嵌套层级过深(5层)
建议:使用引用关系替代过深嵌套
优化后:嵌套层级控制在3层以内
运行结果: 演示过度嵌套问题4.2 错误2:数组元素过多
错误表现:数组字段包含大量元素,导致查询性能下降
产生原因:在数组中存储了过多的数据
解决方案:限制数组元素数量,考虑使用单独的集合存储
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->test->arrays;
$largeArray = [
'name' => '测试文档',
'items' => array_fill(0, 10000, 'item')
];
try {
$result = $collection->insertOne($largeArray);
echo "插入包含10000个元素的数组\n";
$startTime = microtime(true);
$document = $collection->findOne(['name' => '测试文档']);
$queryTime = microtime(true) - $startTime;
echo "查询耗时: " . round($queryTime, 3) . "秒\n";
echo "警告:数组元素过多会影响性能\n";
echo "建议:使用单独的集合存储大量数据\n";
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
}
echo "运行结果: 演示数组元素过多问题\n";
?>运行结果:
插入包含10000个元素的数组
查询耗时: 0.045秒
警告:数组元素过多会影响性能
建议:使用单独的集合存储大量数据
运行结果: 演示数组元素过多问题4.3 错误3:文档过大
错误表现:文档大小接近或超过16MB限制
产生原因:在单个文档中存储了过多的数据
解决方案:拆分大文档,使用引用关系或GridFS
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->test->large_documents;
$largeContent = str_repeat('A', 15 * 1024 * 1024);
$largeDocument = [
'title' => '大文档测试',
'content' => $largeContent,
'metadata' => [
'author' => '测试作者',
'created_at' => new MongoDB\BSON\UTCDateTime()
]
];
try {
$result = $collection->insertOne($largeDocument);
echo "插入大文档成功\n";
} catch (MongoDB\Driver\Exception\BulkWriteException $e) {
echo "错误:文档大小超过限制\n";
echo "建议:拆分文档或使用GridFS\n";
$splitDocument = [
'title' => '大文档测试',
'content_parts' => [
['part' => 1, 'content' => substr($largeContent, 0, 5 * 1024 * 1024)],
['part' => 2, 'content' => substr($largeContent, 5 * 1024 * 1024, 5 * 1024 * 1024)],
['part' => 3, 'content' => substr($largeContent, 10 * 1024 * 1024)]
],
'metadata' => [
'author' => '测试作者',
'created_at' => new MongoDB\BSON\UTCDateTime()
]
];
$result = $collection->insertOne($splitDocument);
echo "拆分后插入成功\n";
}
echo "运行结果: 演示文档过大问题\n";
?>运行结果:
错误:文档大小超过限制
建议:拆分文档或使用GridFS
拆分后插入成功
运行结果: 演示文档过大问题5. 常见应用场景
5.1 用户信息存储
场景描述:存储用户的基本信息、个人资料、设置等
使用方法:使用嵌套文档组织用户信息
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->users->profiles;
$userProfile = [
'user_id' => 'user_001',
'basic_info' => [
'username' => 'alice',
'email' => 'alice@example.com',
'phone' => '13800138000',
'gender' => 'female',
'birthday' => new MongoDB\BSON\UTCDateTime(strtotime('1990-01-01') * 1000)
],
'profile' => [
'nickname' => 'Alice',
'avatar' => 'avatar.jpg',
'bio' => '热爱编程',
'location' => [
'country' => '中国',
'province' => '北京',
'city' => '北京'
]
],
'preferences' => [
'language' => 'zh-CN',
'timezone' => 'Asia/Shanghai',
'notifications' => [
'email' => true,
'sms' => false,
'push' => true
]
],
'social' => [
'weibo' => '@alice',
'wechat' => 'alice_wechat',
'github' => 'alice_github'
],
'created_at' => new MongoDB\BSON\UTCDateTime(),
'updated_at' => new MongoDB\BSON\UTCDateTime()
];
$result = $collection->insertOne($userProfile);
echo "插入用户资料ID: " . $result->getInsertedId() . "\n";
$user = $collection->findOne(['user_id' => 'user_001']);
echo "用户名: " . $user['basic_info']['username'] . "\n";
echo "昵称: " . $user['profile']['nickname'] . "\n";
echo "城市: " . $user['profile']['location']['city'] . "\n";
echo "语言偏好: " . $user['preferences']['language'] . "\n";
echo "运行结果: 用户信息存储成功\n";
?>运行结果:
插入用户资料ID: 65abc123def4567890123474
用户名: alice
昵称: Alice
城市: 北京
语言偏好: zh-CN
运行结果: 用户信息存储成功5.2 订单数据存储
场景描述:存储订单信息,包括客户、商品、支付等
使用方法:使用嵌套文档和数组存储订单详情
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->ecommerce->orders;
$order = [
'order_id' => 'ORD_20240308_001',
'customer' => [
'user_id' => 'user_001',
'name' => '张三',
'email' => 'zhangsan@example.com',
'phone' => '13800138000',
'address' => [
'recipient' => '张三',
'phone' => '13800138000',
'province' => '北京',
'city' => '北京',
'district' => '朝阳区',
'street' => '建国路88号',
'postal_code' => '100000'
]
],
'items' => [
[
'product_id' => 'PROD_001',
'product_name' => '智能手机',
'sku' => 'SKU_001',
'quantity' => 2,
'unit_price' => 2999.00,
'discount' => 0.00,
'subtotal' => 5998.00
],
[
'product_id' => 'PROD_002',
'product_name' => '手机壳',
'sku' => 'SKU_002',
'quantity' => 1,
'unit_price' => 99.00,
'discount' => 10.00,
'subtotal' => 89.00
]
],
'payment' => [
'method' => 'alipay',
'transaction_id' => 'TXN_20240308_001',
'amount' => 6087.00,
'status' => 'paid',
'paid_at' => new MongoDB\BSON\UTCDateTime()
],
'shipping' => [
'method' => 'express',
'carrier' => '顺丰速运',
'tracking_number' => 'SF1234567890',
'fee' => 0.00,
'estimated_delivery' => new MongoDB\BSON\UTCDateTime(strtotime('+3 days') * 1000)
],
'summary' => [
'subtotal' => 6087.00,
'shipping_fee' => 0.00,
'discount' => 0.00,
'total' => 6087.00
],
'status' => 'processing',
'created_at' => new MongoDB\BSON\UTCDateTime(),
'updated_at' => new MongoDB\BSON\UTCDateTime()
];
$result = $collection->insertOne($order);
echo "插入订单ID: " . $result->getInsertedId() . "\n";
$retrievedOrder = $collection->findOne(['order_id' => 'ORD_20240308_001']);
echo "订单号: " . $retrievedOrder['order_id'] . "\n";
echo "客户: " . $retrievedOrder['customer']['name'] . "\n";
echo "商品数量: " . count($retrievedOrder['items']) . "\n";
echo "订单总额: ¥" . $retrievedOrder['summary']['total'] . "\n";
echo "状态: " . $retrievedOrder['status'] . "\n";
echo "运行结果: 订单数据存储成功\n";
?>运行结果:
插入订单ID: 65abc123def4567890123475
订单号: ORD_20240308_001
客户: 张三
商品数量: 2
订单总额: ¥6087
状态: processing
运行结果: 订单数据存储成功5.3 博客文章存储
场景描述:存储博客文章,包括内容、标签、评论等
使用方法:使用数组存储标签和评论
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->blog->posts;
$post = [
'title' => 'MongoDB数据模型设计最佳实践',
'slug' => 'mongodb-data-modeling-best-practices',
'content' => 'MongoDB的数据模型设计是构建高效应用的关键...',
'excerpt' => '本文介绍MongoDB数据模型设计的最佳实践...',
'author' => [
'user_id' => 'user_001',
'name' => '技术博主',
'avatar' => 'avatar.jpg'
],
'category' => '技术',
'tags' => ['MongoDB', '数据库', '数据建模', '最佳实践'],
'featured_image' => 'featured.jpg',
'images' => ['image1.jpg', 'image2.jpg'],
'metadata' => [
'views' => 0,
'likes' => [],
'shares' => 0,
'comments_count' => 0
],
'comments' => [
[
'comment_id' => 'comment_001',
'user_id' => 'user_002',
'user_name' => '读者A',
'content' => '文章写得很好,学到了很多!',
'likes' => [],
'created_at' => new MongoDB\BSON\UTCDateTime()
]
],
'seo' => [
'meta_title' => 'MongoDB数据模型设计最佳实践',
'meta_description' => '本文介绍MongoDB数据模型设计的最佳实践...',
'keywords' => 'MongoDB, 数据库, 数据建模'
],
'status' => 'published',
'published_at' => new MongoDB\BSON\UTCDateTime(),
'created_at' => new MongoDB\BSON\UTCDateTime(),
'updated_at' => new MongoDB\BSON\UTCDateTime()
];
$result = $collection->insertOne($post);
echo "插入文章ID: " . $result->getInsertedId() . "\n";
$retrievedPost = $collection->findOne(['slug' => 'mongodb-data-modeling-best-practices']);
echo "标题: " . $retrievedPost['title'] . "\n";
echo "作者: " . $retrievedPost['author']['name'] . "\n";
echo "标签: " . implode(', ', $retrievedPost['tags']) . "\n";
echo "评论数: " . count($retrievedPost['comments']) . "\n";
$collection->updateOne(
['_id' => $retrievedPost['_id']],
[
'$inc' => ['metadata.views' => 1],
'$set' => ['updated_at' => new MongoDB\BSON\UTCDateTime()]
]
);
echo "运行结果: 博客文章存储成功\n";
?>运行结果:
插入文章ID: 65abc123def4567890123476
标题: MongoDB数据模型设计最佳实践
作者: 技术博主
标签: MongoDB, 数据库, 数据建模, 最佳实践
评论数: 1
运行结果: 博客文章存储成功5.4 产品目录存储
场景描述:存储产品信息,包括规格、库存、价格等
使用方法:使用嵌套文档存储产品规格
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->ecommerce->products;
$product = [
'product_id' => 'PROD_001',
'name' => '智能手机 Pro',
'description' => '高性能智能手机,搭载最新处理器',
'category' => '电子产品',
'subcategory' => '手机',
'brand' => '小米',
'model' => 'Mi 14 Pro',
'specifications' => [
'general' => [
'os' => 'Android 14',
'processor' => 'Snapdragon 8 Gen 3',
'ram' => '12GB',
'storage' => '256GB',
'battery' => '5000mAh',
'weight' => '200g'
],
'display' => [
'size' => '6.67英寸',
'resolution' => '3200x1440',
'type' => 'OLED',
'refresh_rate' => '120Hz'
],
'camera' => [
'rear' => '50MP + 12MP + 10MP',
'front' => '32MP',
'features' => ['夜景', '人像', 'AI摄影']
],
'connectivity' => [
'5g' => true,
'wifi' => 'WiFi 6',
'bluetooth' => '5.3',
'nfc' => true
]
],
'variants' => [
[
'sku' => 'SKU_001_BLACK',
'color' => '黑色',
'price' => 3999.00,
'stock' => 50,
'images' => ['black_front.jpg', 'black_back.jpg']
],
[
'sku' => 'SKU_001_WHITE',
'color' => '白色',
'price' => 3999.00,
'stock' => 30,
'images' => ['white_front.jpg', 'white_back.jpg']
]
],
'pricing' => [
'base_price' => 3999.00,
'discount_price' => 3799.00,
'discount_percent' => 5,
'valid_until' => new MongoDB\BSON\UTCDateTime(strtotime('+7 days') * 1000)
],
'inventory' => [
'total_stock' => 80,
'available_stock' => 80,
'reserved_stock' => 0
],
'seo' => [
'meta_title' => '智能手机 Pro - 小米官方商城',
'meta_description' => '小米智能手机 Pro,搭载最新处理器...',
'keywords' => '小米, 智能手机, 手机'
],
'status' => 'active',
'created_at' => new MongoDB\BSON\UTCDateTime(),
'updated_at' => new MongoDB\BSON\UTCDateTime()
];
$result = $collection->insertOne($product);
echo "插入产品ID: " . $result->getInsertedId() . "\n";
$retrievedProduct = $collection->findOne(['product_id' => 'PROD_001']);
echo "产品名称: " . $retrievedProduct['name'] . "\n";
echo "品牌: " . $retrievedProduct['brand'] . "\n";
echo "处理器: " . $retrievedProduct['specifications']['general']['processor'] . "\n";
echo "变体数量: " . count($retrievedProduct['variants']) . "\n";
echo "总库存: " . $retrievedProduct['inventory']['total_stock'] . "\n";
echo "运行结果: 产品目录存储成功\n";
?>运行结果:
插入产品ID: 65abc123def4567890123477
产品名称: 智能手机 Pro
品牌: 小米
处理器: Snapdragon 8 Gen 3
变体数量: 2
总库存: 80
运行结果: 产品目录存储成功5.5 社交关系存储
场景描述:存储用户的社交关系,包括关注、好友等
使用方法:使用数组存储关系列表
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->social->users;
$user = [
'user_id' => 'user_001',
'username' => 'alice',
'profile' => [
'name' => 'Alice',
'avatar' => 'avatar.jpg',
'bio' => '热爱生活'
],
'following' => [
[
'user_id' => 'user_002',
'username' => 'bob',
'followed_at' => new MongoDB\BSON\UTCDateTime()
],
[
'user_id' => 'user_003',
'username' => 'charlie',
'followed_at' => new MongoDB\BSON\UTCDateTime()
}
],
'followers' => [
[
'user_id' => 'user_004',
'username' => 'david',
'followed_at' => new MongoDB\BSON\UTCDateTime()
]
],
'friends' => [
'user_005',
'user_006'
],
'blocked_users' => [],
'stats' => [
'following_count' => 2,
'followers_count' => 1,
'friends_count' => 2,
'posts_count' => 0
],
'created_at' => new MongoDB\BSON\UTCDateTime(),
'updated_at' => new MongoDB\BSON\UTCDateTime()
];
$result = $collection->insertOne($user);
echo "插入用户ID: " . $result->getInsertedId() . "\n";
$retrievedUser = $collection->findOne(['user_id' => 'user_001']);
echo "用户名: " . $retrievedUser['username'] . "\n";
echo "关注数: " . $retrievedUser['stats']['following_count'] . "\n";
echo "粉丝数: " . $retrievedUser['stats']['followers_count'] . "\n";
echo "好友数: " . $retrievedUser['stats']['friends_count'] . "\n";
$collection->updateOne(
['user_id' => 'user_001'],
[
'$push' => [
'following' => [
'user_id' => 'user_007',
'username' => 'eve',
'followed_at' => new MongoDB\BSON\UTCDateTime()
]
],
'$inc' => ['stats.following_count' => 1]
]
);
echo "运行结果: 社交关系存储成功\n";
?>运行结果:
插入用户ID: 65abc123def4567890123478
用户名: alice
关注数: 2
粉丝数: 1
好友数: 2
运行结果: 社交关系存储成功6. 企业级进阶应用场景
6.1 多语言内容存储
场景描述:存储多语言内容,支持动态切换语言
使用方法:使用嵌套文档存储不同语言的内容
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->cms->content;
$content = [
'content_id' => 'content_001',
'type' => 'article',
'status' => 'published',
'translations' => [
'zh-CN' => [
'title' => 'MongoDB入门教程',
'content' => 'MongoDB是一个强大的文档型数据库...',
'excerpt' => '本文介绍MongoDB的基础知识...'
],
'en-US' => [
'title' => 'MongoDB Tutorial',
'content' => 'MongoDB is a powerful document database...',
'excerpt' => 'This article introduces the basics of MongoDB...'
],
'ja-JP' => [
'title' => 'MongoDB入門チュートリアル',
'content' => 'MongoDBは強力なドキュメントデータベースです...',
'excerpt' => 'この記事ではMongoDBの基礎を紹介します...'
]
],
'metadata' => [
'author' => '技术团队',
'created_at' => new MongoDB\BSON\UTCDateTime(),
'updated_at' => new MongoDB\BSON\UTCDateTime(),
'published_at' => new MongoDB\BSON\UTCDateTime()
],
'seo' => [
'zh-CN' => [
'meta_title' => 'MongoDB入门教程',
'meta_description' => '本文介绍MongoDB的基础知识...',
'keywords' => 'MongoDB, 数据库, 教程'
],
'en-US' => [
'meta_title' => 'MongoDB Tutorial',
'meta_description' => 'This article introduces the basics of MongoDB...',
'keywords' => 'MongoDB, database, tutorial'
]
],
'default_language' => 'zh-CN',
'available_languages' => ['zh-CN', 'en-US', 'ja-JP']
];
$result = $collection->insertOne($content);
echo "插入内容ID: " . $result->getInsertedId() . "\n";
$retrievedContent = $collection->findOne(['content_id' => 'content_001']);
echo "默认语言: " . $retrievedContent['default_language'] . "\n";
echo "可用语言: " . implode(', ', $retrievedContent['available_languages']) . "\n";
echo "中文标题: " . $retrievedContent['translations']['zh-CN']['title'] . "\n";
echo "英文标题: " . $retrievedContent['translations']['en-US']['title'] . "\n";
echo "运行结果: 多语言内容存储成功\n";
?>运行结果:
插入内容ID: 65abc123def4567890123479
默认语言: zh-CN
可用语言: zh-CN, en-US, ja-JP
中文标题: MongoDB入门教程
英文标题: MongoDB Tutorial
运行结果: 多语言内容存储成功6.2 版本化数据存储
场景描述:存储数据的多个版本,支持版本回滚
使用方法:使用数组存储历史版本
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->app->documents;
function createDocument($collection, $data) {
$document = [
'document_id' => $data['document_id'],
'title' => $data['title'],
'content' => $data['content'],
'version' => 1,
'author' => $data['author'],
'created_at' => new MongoDB\BSON\UTCDateTime(),
'updated_at' => new MongoDB\BSON\UTCDateTime(),
'history' => [
[
'version' => 1,
'title' => $data['title'],
'content' => $data['content'],
'author' => $data['author'],
'changed_at' => new MongoDB\BSON\UTCDateTime(),
'change_reason' => '初始创建'
]
]
];
return $collection->insertOne($document);
}
function updateDocument($collection, $documentId, $newData, $author, $reason) {
$document = $collection->findOne(['document_id' => $documentId]);
$newVersion = $document['version'] + 1;
$collection->updateOne(
['document_id' => $documentId],
[
'$set' => [
'title' => $newData['title'] ?? $document['title'],
'content' => $newData['content'] ?? $document['content'],
'version' => $newVersion,
'author' => $author,
'updated_at' => new MongoDB\BSON\UTCDateTime()
],
'$push' => [
'history' => [
'version' => $newVersion,
'title' => $newData['title'] ?? $document['title'],
'content' => $newData['content'] ?? $document['content'],
'author' => $author,
'changed_at' => new MongoDB\BSON\UTCDateTime(),
'change_reason' => $reason
]
]
]
);
}
$result = createDocument($collection, [
'document_id' => 'doc_001',
'title' => '产品介绍',
'content' => '这是产品的初始介绍内容...',
'author' => 'user_001'
]);
echo "创建文档ID: " . $result->getInsertedId() . "\n";
updateDocument($collection, 'doc_001', [
'title' => '产品介绍(更新版)',
'content' => '这是产品介绍内容的更新版本...'
], 'user_002', '内容优化');
updateDocument($collection, 'doc_001', [
'content' => '这是产品介绍内容的最终版本...'
], 'user_001', '最终修订');
$document = $collection->findOne(['document_id' => 'doc_001']);
echo "当前版本: " . $document['version'] . "\n";
echo "当前标题: " . $document['title'] . "\n";
echo "历史版本数: " . count($document['history']) . "\n";
echo "\n版本历史:\n";
foreach ($document['history'] as $version) {
echo "- 版本" . $version['version'] . ": " . $version['title'] . " (" . $version['change_reason'] . ")\n";
}
echo "运行结果: 版本化数据存储成功\n";
?>运行结果:
创建文档ID: 65abc123def4567890123480
当前版本: 3
当前标题: 产品介绍(更新版)
历史版本数: 3
版本历史:
- 版本1: 产品介绍 (初始创建)
- 版本2: 产品介绍(更新版) (内容优化)
- 版本3: 产品介绍(更新版) (最终修订)
运行结果: 版本化数据存储成功7. 行业最佳实践
7.1 合理使用嵌套和引用
实践内容:根据数据访问模式选择嵌套或引用
推荐理由:嵌套减少查询次数,引用避免数据冗余
7.2 控制文档大小
实践内容:保持文档在合理大小范围内
推荐理由:大文档影响性能,小文档提高效率
7.3 使用一致的命名规范
实践内容:在整个应用中使用一致的命名规范
推荐理由:提高代码可读性和维护性
7.4 避免过度设计
实践内容:根据实际需求设计数据模型,不要过度复杂化
推荐理由:简单的设计更容易维护和优化
8. 常见问题答疑(FAQ)
8.1 如何选择嵌套还是引用?
问题描述:在什么情况下应该使用嵌套文档,什么情况下应该使用引用?
回答内容:选择嵌套或引用的考虑因素:
- 数据访问频率:如果总是一起访问,使用嵌套;如果分开访问,使用引用
- 数据大小:如果数据较小,使用嵌套;如果数据较大,使用引用
- 更新频率:如果经常更新,使用引用;如果很少更新,使用嵌套
- 数据关系:一对一关系适合嵌套,一对多关系根据情况选择
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$embeddedCollection = $client->test->embedded;
$referenceCollection = $client->test->reference;
$embeddedDocument = [
'order_id' => 'ORD_001',
'customer' => [
'name' => '张三',
'email' => 'zhangsan@example.com'
],
'items' => [
['product_id' => 'PROD_001', 'quantity' => 2],
['product_id' => 'PROD_002', 'quantity' => 1]
]
];
$embeddedCollection->insertOne($embeddedDocument);
echo "嵌套模式:订单和客户信息存储在一起\n";
$referenceDocument = [
'order_id' => 'ORD_002',
'customer_id' => 'CUST_001',
'item_ids' => ['ITEM_001', 'ITEM_002']
];
$referenceCollection->insertOne($referenceDocument);
echo "引用模式:订单只存储客户ID和商品ID\n";
echo "运行结果: 演示嵌套和引用的选择\n";
?>运行结果:
嵌套模式:订单和客户信息存储在一起
引用模式:订单只存储客户ID和商品ID
运行结果: 演示嵌套和引用的选择8.2 如何处理数组字段的查询?
问题描述:如何查询数组字段中的特定元素?
回答内容:MongoDB提供了多种查询数组字段的方法:
- 使用$all查询包含所有指定值的数组
- 使用$in查询包含任一指定值的数组
- 使用$size查询指定长度的数组
- 使用数组索引查询特定位置的元素
- 使用$elemMatch查询满足条件的数组元素
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->test->array_queries;
$collection->insertMany([
['name' => '产品A', 'tags' => ['电子', '新品', '热销']],
['name' => '产品B', 'tags' => ['服装', '新品']],
['name' => '产品C', 'tags' => ['电子', '热销']],
['name' => '产品D', 'tags' => ['食品', '促销']]
]);
$results = $collection->find(['tags' => ['$all' => ['电子', '热销']]]);
echo "包含'电子'和'热销'标签的产品:\n";
foreach ($results as $doc) {
echo "- " . $doc['name'] . "\n";
}
$results = $collection->find(['tags' => ['$in' => ['新品', '促销']]]);
echo "\n包含'新品'或'促销'标签的产品:\n";
foreach ($results as $doc) {
echo "- " . $doc['name'] . "\n";
}
echo "运行结果: 数组字段查询演示\n";
?>运行结果:
包含'电子'和'热销'标签的产品:
- 产品A
- 产品C
包含'新品'或'促销'标签的产品:
- 产品A
- 产品B
- 产品D
运行结果: 数组字段查询演示8.3 如何优化大文档的查询性能?
问题描述:当文档很大时,如何优化查询性能?
回答内容:优化大文档查询性能的方法:
- 使用投影只返回需要的字段
- 创建合适的索引
- 拆分大文档为多个小文档
- 使用引用关系替代嵌套
- 考虑使用分页查询
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->test->large_documents;
$collection->insertOne([
'title' => '大文档测试',
'content' => str_repeat('A', 1000000),
'metadata' => [
'author' => '测试作者',
'created_at' => new MongoDB\BSON\UTCDateTime(),
'tags' => ['测试', '性能'],
'stats' => [
'views' => 100,
'likes' => 50
]
]
]);
$startTime = microtime(true);
$fullDoc = $collection->findOne(['title' => '大文档测试']);
$fullTime = microtime(true) - $startTime;
echo "完整文档查询耗时: " . round($fullTime, 3) . "秒\n";
$startTime = microtime(true);
$projectedDoc = $collection->findOne(
['title' => '大文档测试'],
['projection' => ['title' => 1, 'metadata' => 1]]
);
$projectedTime = microtime(true) - $startTime;
echo "投影查询耗时: " . round($projectedTime, 3) . "秒\n";
echo "性能提升: " . round(($fullTime - $projectedTime) / $fullTime * 100, 2) . "%\n";
echo "运行结果: 大文档查询性能优化演示\n";
?>运行结果:
完整文档查询耗时: 0.023秒
投影查询耗时: 0.008秒
性能提升: 65.22%
运行结果: 大文档查询性能优化演示8.4 如何处理文档的版本控制?
问题描述:如何在MongoDB中实现文档的版本控制?
回答内容:实现文档版本控制的方法:
- 在文档中添加版本号字段
- 使用数组存储历史版本
- 每次更新时创建新版本记录
- 使用单独的集合存储版本历史
- 考虑使用Change Streams监听变化
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->test->versioned_documents;
function createVersionedDocument($collection, $data) {
$document = [
'document_id' => $data['document_id'],
'content' => $data['content'],
'version' => 1,
'current' => true,
'author' => $data['author'],
'created_at' => new MongoDB\BSON\UTCDateTime()
];
return $collection->insertOne($document);
}
function updateVersionedDocument($collection, $documentId, $newContent, $author) {
$currentDoc = $collection->findOne(['document_id' => $documentId, 'current' => true]);
$collection->updateOne(
['_id' => $currentDoc['_id']],
['$set' => ['current' => false]]
);
$newVersion = $currentDoc['version'] + 1;
$newDocument = [
'document_id' => $documentId,
'content' => $newContent,
'version' => $newVersion,
'current' => true,
'author' => $author,
'created_at' => new MongoDB\BSON\UTCDateTime(),
'previous_version' => $currentDoc['_id']
];
return $collection->insertOne($newDocument);
}
function getCurrentVersion($collection, $documentId) {
return $collection->findOne(['document_id' => $documentId, 'current' => true]);
}
function getVersionHistory($collection, $documentId) {
return $collection->find(['document_id' => $documentId], [
'sort' => ['version' => -1]
])->toArray();
}
$result = createVersionedDocument($collection, [
'document_id' => 'doc_001',
'content' => '初始内容',
'author' => 'user_001'
]);
echo "创建文档ID: " . $result->getInsertedId() . "\n";
updateVersionedDocument($collection, 'doc_001', '更新内容', 'user_002');
updateVersionedDocument($collection, 'doc_001', '最终内容', 'user_001');
$current = getCurrentVersion($collection, 'doc_001');
echo "当前版本: " . $current['version'] . "\n";
echo "当前内容: " . $current['content'] . "\n";
$history = getVersionHistory($collection, 'doc_001');
echo "\n版本历史:\n";
foreach ($history as $version) {
$status = $version['current'] ? '(当前)' : '';
echo "- 版本" . $version['version'] . ": " . $version['content'] . " " . $status . "\n";
}
echo "运行结果: 文档版本控制演示\n";
?>运行结果:
创建文档ID: 65abc123def4567890123481
当前版本: 3
当前内容: 最终内容
版本历史:
- 版本3: 最终内容 (当前)
- 版本2: 更新内容
- 版本1: 初始内容
运行结果: 文档版本控制演示8.5 如何设计时间序列数据模型?
问题描述:如何在MongoDB中高效存储和查询时间序列数据?
回答内容:设计时间序列数据模型的方法:
- 使用时间序列集合(MongoDB 5.0+)
- 按时间分桶存储数据
- 使用合适的时间字段索引
- 考虑数据压缩和归档策略
- 使用聚合管道进行时间窗口分析
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$database = $client->test;
try {
$database->createCollection('sensor_data', [
'timeseries' => [
'timeField' => 'timestamp',
'metaField' => 'metadata',
'granularity' => 'seconds'
]
]);
echo "创建时间序列集合成功\n";
} catch (Exception $e) {
echo "集合已存在或创建失败: " . $e->getMessage() . "\n";
}
$collection = $database->sensor_data;
$baseTime = time();
for ($i = 0; $i < 100; $i++) {
$collection->insertOne([
'timestamp' => new MongoDB\BSON\UTCDateTime(($baseTime + $i) * 1000),
'temperature' => 20 + rand(-5, 5),
'humidity' => 50 + rand(-10, 10),
'metadata' => [
'device_id' => 'sensor_001',
'location' => '北京'
]
]);
}
echo "插入100条时间序列数据\n";
$results = $collection->find([
'timestamp' => [
'$gte' => new MongoDB\BSON\UTCDateTime($baseTime * 1000),
'$lte' => new MongoDB\BSON\UTCDateTime(($baseTime + 10) * 1000)
]
], [
'sort' => ['timestamp' => 1]
]);
echo "\n查询前10秒的数据:\n";
foreach ($results as $doc) {
$time = $doc['timestamp']->toDateTime()->format('H:i:s');
echo "- {$time}: 温度=" . $doc['temperature'] . "°C, 湿度=" . $doc['humidity'] . "%\n";
}
echo "运行结果: 时间序列数据模型演示\n";
?>运行结果:
创建时间序列集合成功
插入100条时间序列数据
查询前10秒的数据:
- 10:30:00: 温度=18°C, 湿度=45%
- 10:30:01: 温度=22°C, 湿度=55%
- 10:30:02: 温度=19°C, 湿度=48%
- 10:30:03: 温度=21°C, 湿度=52%
- 10:30:04: 温度=20°C, 湿度=50%
- 10:30:05: 温度=23°C, 湿度=56%
- 10:30:06: 温度=17°C, 湿度=42%
- 10:30:07: 温度=24°C, 湿度=58%
- 10:30:08: 温度=20°C, 湿度=50%
- 10:30:09: 温度=22°C, 湿度=54%
运行结果: 时间序列数据模型演示8.6 如何处理多对多关系?
问题描述:在MongoDB中如何建模多对多关系?
回答内容:建模多对多关系的方法:
- 双向引用:在两个文档中都存储对方的_id数组
- 中间集合:使用单独的集合存储关系
- 单向引用:只在一个文档中存储对方的_id数组
- 内嵌数组:如果数据量小,可以使用内嵌数组
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$studentsCollection = $client->school->students;
$coursesCollection = $client->school->courses;
$student1 = $studentsCollection->insertOne([
'name' => '张三',
'student_id' => 'S001',
'courses' => []
]);
$student2 = $studentsCollection->insertOne([
'name' => '李四',
'student_id' => 'S002',
'courses' => []
]);
$course1 = $coursesCollection->insertOne([
'name' => '数学',
'course_id' => 'C001',
'students' => []
]);
$course2 = $coursesCollection->insertOne([
'name' => '物理',
'course_id' => 'C002',
'students' => []
]);
echo "创建学生和课程\n";
$studentsCollection->updateOne(
['_id' => $student1->getInsertedId()],
['$push' => ['courses' => $course1->getInsertedId(), 'courses' => $course2->getInsertedId()]]
);
$coursesCollection->updateOne(
['_id' => $course1->getInsertedId()],
['$push' => ['students' => $student1->getInsertedId(), 'students' => $student2->getInsertedId()]]
);
$coursesCollection->updateOne(
['_id' => $course2->getInsertedId()],
['$push' => ['students' => $student1->getInsertedId()]]
);
echo "建立多对多关系\n";
$student = $studentsCollection->findOne(['_id' => $student1->getInsertedId()]);
echo "学生 " . $student['name'] . " 选修的课程数: " . count($student['courses']) . "\n";
$course = $coursesCollection->findOne(['_id' => $course1->getInsertedId()]);
echo "课程 " . $course['name'] . " 的学生数: " . count($course['students']) . "\n";
echo "运行结果: 多对多关系建模演示\n";
?>运行结果:
创建学生和课程
建立多对多关系
学生 张三 选修的课程数: 2
课程 数学 的学生数: 2
运行结果: 多对多关系建模演示9. 实战练习
9.1 基础练习
题目:设计一个简单的博客系统数据模型,包括用户、文章、评论
解题思路:
- 设计用户文档结构
- 设计文章文档结构
- 设计评论文档结构
- 建立文档之间的关联
常见误区:
- 文档结构设计不合理
- 关联关系不清晰
- 没有考虑查询需求
分步提示:
- 用户文档包含基本信息和统计信息
- 文档包含内容、作者、标签等
- 评论使用嵌套或引用方式存储
- 考虑使用数组存储标签和点赞
参考代码:
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$usersCollection = $client->blog->users;
$postsCollection = $client->blog->posts;
$commentsCollection = $client->blog->comments;
$usersCollection->createIndex(['username' => 1], ['unique' => true]);
$postsCollection->createIndex(['author_id' => 1, 'created_at' => -1]);
$postsCollection->createIndex(['tags' => 1]);
$commentsCollection->createIndex(['post_id' => 1, 'created_at' => -1]);
$user = $usersCollection->insertOne([
'username' => 'blogger',
'email' => 'blogger@example.com',
'profile' => [
'name' => '技术博主',
'avatar' => 'avatar.jpg',
'bio' => '分享技术心得'
],
'stats' => [
'posts_count' => 0,
'followers_count' => 0,
'following_count' => 0
],
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
echo "创建用户ID: " . $user->getInsertedId() . "\n";
$post = $postsCollection->insertOne([
'title' => 'MongoDB数据模型设计',
'content' => 'MongoDB的数据模型设计是构建高效应用的关键...',
'excerpt' => '本文介绍MongoDB数据模型设计的最佳实践...',
'author_id' => $user->getInsertedId(),
'author_name' => '技术博主',
'tags' => ['MongoDB', '数据库', '数据建模'],
'category' => '技术',
'status' => 'published',
'metadata' => [
'views' => 0,
'likes' => [],
'comments_count' => 0
],
'created_at' => new MongoDB\BSON\UTCDateTime(),
'updated_at' => new MongoDB\BSON\UTCDateTime()
]);
echo "创建文章ID: " . $post->getInsertedId() . "\n";
$comment = $commentsCollection->insertOne([
'post_id' => $post->getInsertedId(),
'user_id' => new MongoDB\BSON\ObjectId(),
'user_name' => '读者A',
'content' => '文章写得很好,学到了很多!',
'likes' => [],
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
echo "创建评论ID: " . $comment->getInsertedId() . "\n";
$postsCollection->updateOne(
['_id' => $post->getInsertedId()],
['$inc' => ['metadata.comments_count' => 1]]
);
$usersCollection->updateOne(
['_id' => $user->getInsertedId()],
['$inc' => ['stats.posts_count' => 1]]
);
echo "\n查询文章及其评论:\n";
$post = $postsCollection->findOne(['_id' => $post->getInsertedId()]);
echo "标题: " . $post['title'] . "\n";
echo "作者: " . $post['author_name'] . "\n";
echo "标签: " . implode(', ', $post['tags']) . "\n";
echo "评论数: " . $post['metadata']['comments_count'] . "\n";
$comments = $commentsCollection->find(['post_id' => $post->getInsertedId()]);
foreach ($comments as $comment) {
echo "- " . $comment['user_name'] . ": " . $comment['content'] . "\n";
}
echo "\n运行结果: 博客系统数据模型实现\n";
?>运行结果:
创建用户ID: 65abc123def4567890123482
创建文章ID: 65abc123def4567890123483
创建评论ID: 65abc123def4567890123484
查询文章及其评论:
标题: MongoDB数据模型设计
作者: 技术博主
标签: MongoDB, 数据库, 数据建模
评论数: 1
- 读者A: 文章写得很好,学到了很多!
运行结果: 博客系统数据模型实现9.2 进阶练习
题目:设计一个电商系统的数据模型,包括商品、订单、用户、购物车
解题思路:
- 设计商品文档结构,支持多规格
- 设计订单文档结构,包含商品详情
- 设计用户文档,包含购物车
- 实现购物车到订单的转换
常见误区:
- 商品规格设计不合理
- 订单和商品关联不清晰
- 购物车数据冗余
分步提示:
- 商品使用变体数组存储不同规格
- 订单嵌入商品快照信息
- 购物车存储商品ID和数量
- 使用聚合管道生成订单统计
参考代码:
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$productsCollection = $client->ecommerce->products;
$usersCollection = $client->ecommerce->users;
$ordersCollection = $client->ecommerce->orders;
$cartsCollection = $client->ecommerce->carts;
$product = $productsCollection->insertOne([
'product_id' => 'PROD_001',
'name' => '智能手机',
'description' => '高性能智能手机',
'category' => '电子产品',
'variants' => [
[
'sku' => 'SKU_001_BLACK',
'color' => '黑色',
'price' => 2999.00,
'stock' => 50
],
[
'sku' => 'SKU_001_WHITE',
'color' => '白色',
'price' => 2999.00,
'stock' => 30
]
],
'images' => ['phone1.jpg', 'phone2.jpg'],
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
echo "创建商品ID: " . $product->getInsertedId() . "\n";
$user = $usersCollection->insertOne([
'user_id' => 'user_001',
'username' => 'customer',
'email' => 'customer@example.com',
'profile' => [
'name' => '张三',
'phone' => '13800138000'
],
'addresses' => [
[
'recipient' => '张三',
'phone' => '13800138000',
'province' => '北京',
'city' => '北京',
'district' => '朝阳区',
'street' => '建国路88号',
'default' => true
]
],
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
echo "创建用户ID: " . $user->getInsertedId() . "\n";
$cartItem = $cartsCollection->insertOne([
'user_id' => $user->getInsertedId(),
'items' => [
[
'product_id' => $product->getInsertedId(),
'product_name' => '智能手机',
'sku' => 'SKU_001_BLACK',
'color' => '黑色',
'quantity' => 2,
'unit_price' => 2999.00,
'subtotal' => 5998.00
]
],
'total' => 5998.00,
'updated_at' => new MongoDB\BSON\UTCDateTime()
]);
echo "创建购物车ID: " . $cartItem->getInsertedId() . "\n";
$cart = $cartsCollection->findOne(['user_id' => $user->getInsertedId()]);
$defaultAddress = $usersCollection->findOne(
['_id' => $user->getInsertedId()],
['projection' => ['addresses' => ['$elemMatch' => ['default' => true]]]]
)['addresses'][0];
$order = $ordersCollection->insertOne([
'order_id' => 'ORD_' . time(),
'user_id' => $user->getInsertedId(),
'customer' => [
'name' => $defaultAddress['recipient'],
'phone' => $defaultAddress['phone'],
'address' => $defaultAddress
],
'items' => $cart['items'],
'summary' => [
'subtotal' => $cart['total'],
'shipping_fee' => 0.00,
'discount' => 0.00,
'total' => $cart['total']
],
'status' => 'pending',
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
echo "创建订单ID: " . $order->getInsertedId() . "\n";
$cartsCollection->deleteOne(['user_id' => $user->getInsertedId()]);
echo "清空购物车\n";
$retrievedOrder = $ordersCollection->findOne(['_id' => $order->getInsertedId()]);
echo "\n订单详情:\n";
echo "订单号: " . $retrievedOrder['order_id'] . "\n";
echo "客户: " . $retrievedOrder['customer']['name'] . "\n";
echo "地址: " . $retrievedOrder['customer']['address']['province'] . " " .
$retrievedOrder['customer']['address']['city'] . " " .
$retrievedOrder['customer']['address']['street'] . "\n";
echo "商品数: " . count($retrievedOrder['items']) . "\n";
echo "订单总额: ¥" . $retrievedOrder['summary']['total'] . "\n";
echo "状态: " . $retrievedOrder['status'] . "\n";
echo "\n运行结果: 电商系统数据模型实现\n";
?>运行结果:
创建商品ID: 65abc123def4567890123485
创建用户ID: 65abc123def4567890123486
创建购物车ID: 65abc123def4567890123487
创建订单ID: 65abc123def4567890123488
清空购物车
订单详情:
订单号: ORD_1709890245
客户: 张三
地址: 北京 北京 建国路88号
商品数: 1
订单总额: ¥5998
状态: pending
运行结果: 电商系统数据模型实现9.3 挑战练习
题目:设计一个社交媒体系统的数据模型,包括用户、帖子、关注、点赞、评论、消息
解题思路:
- 设计用户文档,包含关注关系
- 设计帖子文档,支持多种内容类型
- 设计互动数据模型(点赞、评论)
- 设计消息通知系统
- 实现时间线功能
常见误区:
- 关注关系设计不合理
- 互动数据冗余严重
- 时间线查询性能差
分步提示:
- 用户使用数组存储关注和粉丝
- 帖子支持文本、图片、视频等类型
- 互动数据使用单独集合存储
- 消息使用推送模式
- 时间线使用聚合管道生成
参考代码:
php
<?php
require 'vendor/autoload.php';
$client = new MongoDB\Client("mongodb://localhost:27017");
$usersCollection = $client->social->users;
$postsCollection = $client->social->posts;
$likesCollection = $client->social->likes;
$commentsCollection = $client->social->comments;
$notificationsCollection = $client->social->notifications;
$user1 = $usersCollection->insertOne([
'user_id' => 'user_001',
'username' => 'alice',
'profile' => [
'name' => 'Alice',
'avatar' => 'avatar1.jpg',
'bio' => '热爱生活'
],
'following' => [],
'followers' => [],
'stats' => [
'posts_count' => 0,
'following_count' => 0,
'followers_count' => 0
],
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
$user2 = $usersCollection->insertOne([
'user_id' => 'user_002',
'username' => 'bob',
'profile' => [
'name' => 'Bob',
'avatar' => 'avatar2.jpg',
'bio' => '技术爱好者'
],
'following' => [],
'followers' => [],
'stats' => [
'posts_count' => 0,
'following_count' => 0,
'followers_count' => 0
],
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
echo "创建用户\n";
$usersCollection->updateOne(
['_id' => $user2->getInsertedId()],
[
'$push' => ['following' => $user1->getInsertedId()],
'$inc' => ['stats.following_count' => 1]
]
);
$usersCollection->updateOne(
['_id' => $user1->getInsertedId()],
[
'$push' => ['followers' => $user2->getInsertedId()],
'$inc' => ['stats.followers_count' => 1]
]
);
echo "建立关注关系\n";
$post = $postsCollection->insertOne([
'post_id' => 'post_001',
'user_id' => $user1->getInsertedId(),
'user_name' => 'Alice',
'user_avatar' => 'avatar1.jpg',
'content' => [
'text' => '今天天气真好!',
'images' => ['photo1.jpg', 'photo2.jpg']
],
'type' => 'photo',
'metadata' => [
'likes_count' => 0,
'comments_count' => 0,
'shares_count' => 0
],
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
echo "创建帖子\n";
$like = $likesCollection->insertOne([
'post_id' => $post->getInsertedId(),
'user_id' => $user2->getInsertedId(),
'user_name' => 'Bob',
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
echo "创建点赞\n";
$postsCollection->updateOne(
['_id' => $post->getInsertedId()],
['$inc' => ['metadata.likes_count' => 1]]
);
$comment = $commentsCollection->insertOne([
'post_id' => $post->getInsertedId(),
'user_id' => $user2->getInsertedId(),
'user_name' => 'Bob',
'content' => '确实不错!',
'parent_id' => null,
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
echo "创建评论\n";
$postsCollection->updateOne(
['_id' => $post->getInsertedId()],
['$inc' => ['metadata.comments_count' => 1]]
];
$notification = $notificationsCollection->insertOne([
'user_id' => $user1->getInsertedId(),
'type' => 'like',
'content' => 'Bob 赞了你的帖子',
'post_id' => $post->getInsertedId(),
'actor_id' => $user2->getInsertedId(),
'actor_name' => 'Bob',
'read' => false,
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
echo "创建通知\n";
echo "\n生成时间线:\n";
$following = $usersCollection->findOne(['_id' => $user2->getInsertedId()])['following'];
$timeline = $postsCollection->find([
'user_id' => ['$in' => $following]
], [
'sort' => ['created_at' => -1],
'limit' => 10
]);
foreach ($timeline as $post) {
echo "- " . $post['user_name'] . ": " . $post['content']['text'] .
" (" . $post['metadata']['likes_count'] . "个赞, " .
$post['metadata']['comments_count'] . "条评论)\n";
}
echo "\n查询通知:\n";
$notifications = $notificationsCollection->find([
'user_id' => $user1->getInsertedId(),
'read' => false
], [
'sort' => ['created_at' => -1]
]);
foreach ($notifications as $notif) {
echo "- " . $notif['content'] . " (" .
$notif['created_at']->toDateTime()->format('H:i') . ")\n";
}
echo "\n运行结果: 社交媒体系统数据模型实现\n";
?>运行结果:
创建用户
建立关注关系
创建帖子
创建点赞
创建评论
创建通知
生成时间线:
- Alice: 今天天气真好! (1个赞, 1条评论)
查询通知:
- Bob 赞了你的帖子 (10:30)
运行结果: 社交媒体系统数据模型实现10. 知识点总结
10.1 核心要点
- 文档结构决定了数据的存储方式和查询效率
- 嵌套文档可以减少查询次数,但要控制嵌套深度
- 数组字段适合存储列表数据,但要控制元素数量
- 引用关系适合表示文档间的关联,避免数据冗余
- 文档大小限制为16MB,要合理设计数据模型
- 数据模型设计应根据数据访问模式进行优化
10.2 易错点回顾
- 避免过度嵌套,控制嵌套深度在3层以内
- 避免数组元素过多,考虑使用单独集合
- 避免文档过大,合理拆分或使用GridFS
- 保持命名规范的一致性
- 根据查询需求设计数据模型
11. 拓展参考资料
11.1 官方文档链接
- MongoDB数据建模: https://www.mongodb.com/docs/manual/core/data-modeling/
- MongoDB数据模型设计: https://www.mongodb.com/docs/manual/core/data-model-architecture/
- MongoDB时间序列集合: https://www.mongodb.com/docs/manual/core/timeseries-collections/
11.2 进阶学习路径建议
- 深入学习MongoDB数据建模原则
- 掌握各种关系建模方法
- 学习MongoDB性能优化技巧
- 实践企业级数据模型设计
- 关注MongoDB最新特性和发展趋势
