Skip to content

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 如何选择嵌套还是引用?

问题描述:在什么情况下应该使用嵌套文档,什么情况下应该使用引用?

回答内容:选择嵌套或引用的考虑因素:

  1. 数据访问频率:如果总是一起访问,使用嵌套;如果分开访问,使用引用
  2. 数据大小:如果数据较小,使用嵌套;如果数据较大,使用引用
  3. 更新频率:如果经常更新,使用引用;如果很少更新,使用嵌套
  4. 数据关系:一对一关系适合嵌套,一对多关系根据情况选择
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提供了多种查询数组字段的方法:

  1. 使用$all查询包含所有指定值的数组
  2. 使用$in查询包含任一指定值的数组
  3. 使用$size查询指定长度的数组
  4. 使用数组索引查询特定位置的元素
  5. 使用$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 如何优化大文档的查询性能?

问题描述:当文档很大时,如何优化查询性能?

回答内容:优化大文档查询性能的方法:

  1. 使用投影只返回需要的字段
  2. 创建合适的索引
  3. 拆分大文档为多个小文档
  4. 使用引用关系替代嵌套
  5. 考虑使用分页查询
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中实现文档的版本控制?

回答内容:实现文档版本控制的方法:

  1. 在文档中添加版本号字段
  2. 使用数组存储历史版本
  3. 每次更新时创建新版本记录
  4. 使用单独的集合存储版本历史
  5. 考虑使用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中高效存储和查询时间序列数据?

回答内容:设计时间序列数据模型的方法:

  1. 使用时间序列集合(MongoDB 5.0+)
  2. 按时间分桶存储数据
  3. 使用合适的时间字段索引
  4. 考虑数据压缩和归档策略
  5. 使用聚合管道进行时间窗口分析
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中如何建模多对多关系?

回答内容:建模多对多关系的方法:

  1. 双向引用:在两个文档中都存储对方的_id数组
  2. 中间集合:使用单独的集合存储关系
  3. 单向引用:只在一个文档中存储对方的_id数组
  4. 内嵌数组:如果数据量小,可以使用内嵌数组
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 基础练习

题目:设计一个简单的博客系统数据模型,包括用户、文章、评论

解题思路

  1. 设计用户文档结构
  2. 设计文章文档结构
  3. 设计评论文档结构
  4. 建立文档之间的关联

常见误区

  • 文档结构设计不合理
  • 关联关系不清晰
  • 没有考虑查询需求

分步提示

  1. 用户文档包含基本信息和统计信息
  2. 文档包含内容、作者、标签等
  3. 评论使用嵌套或引用方式存储
  4. 考虑使用数组存储标签和点赞

参考代码

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 进阶练习

题目:设计一个电商系统的数据模型,包括商品、订单、用户、购物车

解题思路

  1. 设计商品文档结构,支持多规格
  2. 设计订单文档结构,包含商品详情
  3. 设计用户文档,包含购物车
  4. 实现购物车到订单的转换

常见误区

  • 商品规格设计不合理
  • 订单和商品关联不清晰
  • 购物车数据冗余

分步提示

  1. 商品使用变体数组存储不同规格
  2. 订单嵌入商品快照信息
  3. 购物车存储商品ID和数量
  4. 使用聚合管道生成订单统计

参考代码

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 挑战练习

题目:设计一个社交媒体系统的数据模型,包括用户、帖子、关注、点赞、评论、消息

解题思路

  1. 设计用户文档,包含关注关系
  2. 设计帖子文档,支持多种内容类型
  3. 设计互动数据模型(点赞、评论)
  4. 设计消息通知系统
  5. 实现时间线功能

常见误区

  • 关注关系设计不合理
  • 互动数据冗余严重
  • 时间线查询性能差

分步提示

  1. 用户使用数组存储关注和粉丝
  2. 帖子支持文本、图片、视频等类型
  3. 互动数据使用单独集合存储
  4. 消息使用推送模式
  5. 时间线使用聚合管道生成

参考代码

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 核心要点

  1. 文档结构决定了数据的存储方式和查询效率
  2. 嵌套文档可以减少查询次数,但要控制嵌套深度
  3. 数组字段适合存储列表数据,但要控制元素数量
  4. 引用关系适合表示文档间的关联,避免数据冗余
  5. 文档大小限制为16MB,要合理设计数据模型
  6. 数据模型设计应根据数据访问模式进行优化

10.2 易错点回顾

  1. 避免过度嵌套,控制嵌套深度在3层以内
  2. 避免数组元素过多,考虑使用单独集合
  3. 避免文档过大,合理拆分或使用GridFS
  4. 保持命名规范的一致性
  5. 根据查询需求设计数据模型

11. 拓展参考资料

11.1 官方文档链接

11.2 进阶学习路径建议

  1. 深入学习MongoDB数据建模原则
  2. 掌握各种关系建模方法
  3. 学习MongoDB性能优化技巧
  4. 实践企业级数据模型设计
  5. 关注MongoDB最新特性和发展趋势