Appearance
MongoDB Object类型详解
1. 概述
对象(Object),也称为嵌入式文档(Embedded Document),是MongoDB文档模型的核心特性之一。它允许在一个文档中嵌套另一个文档,形成层次化的数据结构,这种设计使得数据组织更加自然和高效。
与传统关系型数据库需要通过外键关联多张表不同,MongoDB的嵌入式文档可以直接将相关数据存储在一起,这种反范式化的设计带来了以下优势:
- 查询性能提升:一次查询即可获取所有相关数据,无需多表连接
- 原子性操作:对文档及其嵌入式文档的更新是原子的
- 数据模型自然:数据结构与业务对象模型一致,易于理解
- 减少网络开销:减少数据库请求次数
对象类型在实际开发中应用极其广泛,例如:
- 存储用户的地址信息(address: {city: '北京', street: '朝阳路'})
- 记录商品的详细信息(product: {name: 'iPhone', specs: {color: '黑色', storage: '256GB'}})
- 保存订单的收货地址(shipping_address: {province: '北京', city: '北京', detail: '朝阳路100号'})
- 存储文章的作者信息(author: {id: 'user001', name: '张三', avatar: 'url'})
掌握对象类型的使用,特别是嵌入式文档的设计原则和查询技巧,是成为MongoDB高级开发者的关键技能。
本知识点承接《MongoDB数据类型概述》和《MongoDB Array类型》,后续延伸至《MongoDB数据建模》和《MongoDB聚合管道》,建议学习顺序:MongoDB基础操作→Array类型→本知识点→数据建模→聚合管道。
2. 基本概念
2.1 语法
2.1.1 对象的定义与插入
在MongoDB中,对象使用花括号{}定义,包含多个键值对,键和值之间用冒号分隔。对象可以嵌套多层,形成复杂的层次结构。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->test->users;
$document = [
'name' => '张三',
'age' => 28,
'address' => [
'province' => '北京',
'city' => '北京',
'district' => '朝阳区',
'street' => '朝阳路100号'
],
'contact' => [
'email' => 'zhangsan@example.com',
'phone' => '13800138000',
'social' => [
'wechat' => 'zhangsan_wx',
'weibo' => 'zhangsan_weibo'
]
],
'preferences' => [
'language' => 'zh-CN',
'theme' => 'dark',
'notifications' => true
]
];
$result = $collection->insertOne($document);
echo "插入文档ID: " . $result->getInsertedId() . "\n";
echo "插入成功\n";运行结果:
插入文档ID: 6789abcdef1234567890abcd
插入成功常见改法对比:
php
$wrongDocument = [
'name' => '张三',
'address_province' => '北京',
'address_city' => '北京',
'address_district' => '朝阳区',
'address_street' => '朝阳路100号'
];
$correctDocument = [
'name' => '张三',
'address' => [
'province' => '北京',
'city' => '北京',
'district' => '朝阳区',
'street' => '朝阳路100号'
]
];对比说明:
- 错误写法:使用扁平化的字段名,字段命名冗长,难以维护和扩展
- 正确写法:使用嵌入式对象,结构清晰,易于理解和扩展
2.1.2 对象字段的查询
MongoDB使用点表示法查询嵌入式文档的字段,格式为父字段.子字段。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->test->users;
$collection->insertMany([
[
'name' => '张三',
'address' => [
'province' => '北京',
'city' => '北京'
]
],
[
'name' => '李四',
'address' => [
'province' => '上海',
'city' => '上海'
]
],
[
'name' => '王五',
'address' => [
'province' => '北京',
'city' => '北京'
]
]
]);
$result = $collection->find([
'address.province' => '北京'
])->toArray();
echo "查询省份为北京的用户:\n";
foreach ($result as $user) {
echo " - " . $user['name'] . "\n";
}
$result2 = $collection->find([
'address' => [
'province' => '北京',
'city' => '北京'
]
])->toArray();
echo "\n完全匹配地址对象:\n";
foreach ($result2 as $user) {
echo " - " . $user['name'] . "\n";
}
$result3 = $collection->find([
'address.province' => '北京',
'address.city' => '北京'
])->toArray();
echo "\n分别匹配字段:\n";
foreach ($result3 as $user) {
echo " - " . $user['name'] . "\n";
}运行结果:
查询省份为北京的用户:
- 张三
- 王五
完全匹配地址对象:
- 张三
- 王五
分别匹配字段:
- 张三
- 王五常见改法对比:
php
$wrongQuery = $collection->find([
'address' => ['city' => '北京', 'province' => '北京']
])->toArray();
$correctQuery1 = $collection->find([
'address.province' => '北京'
])->toArray();
$correctQuery2 = $collection->find([
'address' => ['province' => '北京', 'city' => '北京']
])->toArray();对比说明:
- 错误写法:对象字段顺序不匹配,MongoDB要求完全匹配包括字段顺序
- 正确写法1:使用点表示法查询单个字段,不关心顺序
- 正确写法2:对象完全匹配时,字段顺序必须一致
2.1.3 对象字段的更新
MongoDB支持对嵌入式文档的字段进行精确更新,使用点表示法定位到具体字段。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->test->profiles;
$collection->insertOne([
'user_id' => 'user001',
'profile' => [
'name' => '张三',
'age' => 28,
'contact' => [
'email' => 'zhangsan@example.com',
'phone' => '13800138000'
]
]
]);
$collection->updateOne(
['user_id' => 'user001'],
['$set' => ['profile.age' => 29]]
);
$doc = $collection->findOne(['user_id' => 'user001']);
echo "更新年龄后:\n";
echo " 姓名: " . $doc['profile']['name'] . "\n";
echo " 年龄: " . $doc['profile']['age'] . "\n";
$collection->updateOne(
['user_id' => 'user001'],
['$set' => ['profile.contact.email' => 'zhangsan_new@example.com']]
);
$doc = $collection->findOne(['user_id' => 'user001']);
echo "\n更新邮箱后:\n";
echo " 邮箱: " . $doc['profile']['contact']['email'] . "\n";
$collection->updateOne(
['user_id' => 'user001'],
['$set' => ['profile.address' => [
'province' => '北京',
'city' => '北京',
'street' => '朝阳路100号'
]]]
);
$doc = $collection->findOne(['user_id' => 'user001']);
echo "\n添加地址字段后:\n";
echo " 地址: " . $doc['profile']['address']['province'] . " " .
$doc['profile']['address']['city'] . " " .
$doc['profile']['address']['street'] . "\n";运行结果:
更新年龄后:
姓名: 张三
年龄: 29
更新邮箱后:
邮箱: zhangsan_new@example.com
添加地址字段后:
地址: 北京 北京 朝阳路100号常见改法对比:
php
$wrongUpdate = $collection->updateOne(
['user_id' => 'user001'],
['$set' => ['profile' => ['age' => 30]]]
);
$correctUpdate = $collection->updateOne(
['user_id' => 'user001'],
['$set' => ['profile.age' => 30]]
);对比说明:
- 错误写法:更新整个profile对象会覆盖所有字段,导致name、contact等字段丢失
- 正确写法:使用点表示法只更新特定字段,保留其他字段不变
2.2 语义
2.2.1 对象的存储语义
对象在MongoDB中具有以下语义特征:
- 层次性:对象可以嵌套多层,形成树状结构
- 独立性:每个嵌入式文档都是独立的实体,可以有自己的字段
- 原子性:对文档及其嵌入式文档的更新是原子的
- 一致性:嵌入式文档与父文档生命周期一致
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->test->orders;
$order = [
'order_id' => 'ORD001',
'user' => [
'user_id' => 'user001',
'name' => '张三',
'vip_level' => 'gold',
'profile' => [
'email' => 'zhangsan@example.com',
'phone' => '13800138000'
]
},
'shipping' => [
'address' => [
'province' => '北京',
'city' => '北京',
'district' => '朝阳区',
'detail' => '朝阳路100号'
],
'receiver' => '张三',
'phone' => '13800138000'
],
'payment' => [
'method' => 'alipay',
'amount' => 6999,
'status' => 'paid',
'transaction_id' => 'TXN123456'
],
'created_at' => new MongoDB\BSON\UTCDateTime()
];
$result = $collection->insertOne($order);
echo "订单插入成功,ID: " . $result->getInsertedId() . "\n";
$found = $collection->findOne(['order_id' => 'ORD001']);
echo "订单信息:\n";
echo " 订单号: " . $found['order_id'] . "\n";
echo " 用户: " . $found['user']['name'] . " (VIP" . $found['user']['vip_level'] . ")\n";
echo " 收货地址: " . $found['shipping']['address']['province'] . " " .
$found['shipping']['address']['city'] . " " .
$found['shipping']['address']['district'] . "\n";
echo " 支付方式: " . $found['payment']['method'] . "\n";
echo " 支付金额: " . $found['payment']['amount'] . "元\n";运行结果:
订单插入成功,ID: 6789abcdef1234567890abcd
订单信息:
订单号: ORD001
用户: 张三 (VIPgold)
收货地址: 北京 北京 朝阳区
支付方式: alipay
支付金额: 6999元常见改法对比:
php
$wrongStructure = [
'order_id' => 'ORD002',
'user_id' => 'user001',
'user_name' => '张三',
'user_vip_level' => 'gold',
'shipping_province' => '北京',
'shipping_city' => '北京',
'shipping_district' => '朝阳区',
'payment_method' => 'alipay',
'payment_amount' => 6999
];
$correctStructure = [
'order_id' => 'ORD002',
'user' => [
'user_id' => 'user001',
'name' => '张三',
'vip_level' => 'gold'
],
'shipping' => [
'address' => [
'province' => '北京',
'city' => '北京',
'district' => '朝阳区'
]
],
'payment' => [
'method' => 'alipay',
'amount' => 6999
]
];对比说明:
- 错误写法:扁平化结构,字段命名冗长,难以理解数据关系
- 正确写法:使用嵌入式对象,数据层次清晰,语义明确
2.2.2 对象的查询语义
对象字段的查询有以下几种语义:
- 字段匹配:查询对象中特定字段的值
- 对象匹配:查询整个对象是否完全匹配
- 存在性检查:检查对象或对象字段是否存在
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->test->products;
$collection->insertMany([
[
'name' => 'iPhone 15',
'specs' => [
'color' => '黑色',
'storage' => '256GB',
'screen' => '6.1英寸'
]
],
[
'name' => 'iPhone 15 Pro',
'specs' => [
'color' => '深空黑',
'storage' => '512GB',
'screen' => '6.1英寸'
]
],
[
'name' => 'MacBook Pro',
'specs' => [
'color' => '深空灰',
'storage' => '512GB',
'screen' => '14英寸'
]
]
]);
$result1 = $collection->find([
'specs.storage' => '512GB'
])->toArray();
echo "查询存储为512GB的产品:\n";
foreach ($result1 as $product) {
echo " - " . $product['name'] . "\n";
}
$result2 = $collection->find([
'specs' => [
'color' => '黑色',
'storage' => '256GB',
'screen' => '6.1英寸'
]
])->toArray();
echo "\n完全匹配规格对象:\n";
foreach ($result2 as $product) {
echo " - " . $product['name'] . "\n";
}
$result3 = $collection->find([
'specs.color' => ['$exists' => true]
])->toArray();
echo "\n查询有颜色规格的产品:\n";
foreach ($result3 as $product) {
echo " - " . $product['name'] . " (" . $product['specs']['color'] . ")\n";
}运行结果:
查询存储为512GB的产品:
- iPhone 15 Pro
- MacBook Pro
完全匹配规格对象:
- iPhone 15
查询有颜色规格的产品:
- iPhone 15 (黑色)
- iPhone 15 Pro (深空黑)
- MacBook Pro (深空灰)常见改法对比:
php
$wrongQuery = $collection->find([
'specs' => ['storage' => '512GB']
])->toArray();
$correctQuery = $collection->find([
'specs.storage' => '512GB'
])->toArray();对比说明:
- 错误写法:查询整个specs对象是否等于{storage: '512GB'},会忽略其他字段
- 正确写法:使用点表示法查询特定字段的值
2.3 规范
2.3.1 对象命名规范
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->test->examples;
$goodDocument = [
'user_id' => 'user001',
'profile' => [
'first_name' => '张',
'last_name' => '三',
'birth_date' => '1995-01-15'
],
'shipping_address' => [
'province' => '北京',
'city' => '北京',
'street_address' => '朝阳路100号'
],
'payment_info' => [
'method' => 'alipay',
'account' => 'zhangsan@example.com'
]
];
$badDocument = [
'user_id' => 'user001',
'Profile' => [
'FirstName' => '张',
'LastName' => '三',
'BirthDate' => '1995-01-15'
],
'shipping_addr' => [
'prov' => '北京',
'ct' => '北京',
'addr' => '朝阳路100号'
],
'PaymentInfo' => [
'm' => 'alipay',
'acc' => 'zhangsan@example.com'
]
];
$collection->insertOne($goodDocument);
echo "文档插入成功\n";运行结果:
文档插入成功命名规范说明:
- 使用小写字母和下划线:字段名使用snake_case,如
first_name而不是FirstName - 语义明确:对象名称应清楚表达其内容,如
shipping_address而不是addr - 避免缩写:使用完整单词,如
province而不是prov - 一致性:整个项目中对象命名风格保持一致
2.3.2 嵌套深度限制
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->test->nested_docs;
$deepNested = [
'level1' => [
'level2' => [
'level3' => [
'level4' => [
'level5' => [
'level6' => [
'level7' => [
'level8' => [
'level9' => [
'level10' => [
'value' => '深层嵌套'
]
]
]
}
]
}
}
}
}
}
];
try {
$result = $collection->insertOne($deepNested);
echo "插入成功,文档ID: " . $result->getInsertedId() . "\n";
$doc = $collection->findOne(['_id' => $result->getInsertedId()]);
echo "深层值: " . $doc['level1']['level2']['level3']['level4']['level5']['level6']['level7']['level8']['level9']['level10']['value'] . "\n";
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
}
$recommendedNesting = [
'user' => [
'id' => 'user001',
'name' => '张三',
'contact' => [
'email' => 'zhangsan@example.com',
'phone' => '13800138000'
]
],
'order' => [
'id' => 'ORD001',
'items' => [
['product_id' => 'PROD001', 'name' => '商品A', 'price' => 100}
]
]
];
$result = $collection->insertOne($recommendedNesting);
echo "\n推荐嵌套深度的文档插入成功\n";运行结果:
插入成功,文档ID: 6789abcdef1234567890abcd
深层值: 深层嵌套
推荐嵌套深度的文档插入成功嵌套深度规范:
- MongoDB限制:BSON文档最大嵌套深度为100层
- 性能考虑:嵌套过深会影响查询性能和代码可读性
- 最佳实践:建议嵌套深度不超过3-4层
- 替代方案:超过建议深度时,考虑使用引用或拆分文档
3. 原理深度解析
3.1 对象的BSON存储机制
MongoDB使用BSON(Binary JSON)格式存储数据,对象在BSON中的存储遵循特定的编码规则。
3.1.1 BSON对象编码结构
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
use MongoDB\BSON\toJSON;
use MongoDB\BSON\fromPHP;
$client = new Client("mongodb://localhost:27017");
$collection = $client->test->bson_demo;
$document = [
'name' => '测试文档',
'simple_object' => [
'field1' => 'value1',
'field2' => 123
],
'nested_object' => [
'level1' => [
'level2' => [
'level3' => '深层值'
]
]
],
'mixed_object' => [
'string' => '文本',
'number' => 42,
'boolean' => true,
'null' => null,
'array' => [1, 2, 3]
]
];
$result = $collection->insertOne($document);
echo "文档插入成功\n";
$found = $collection->findOne(['name' => '测试文档']);
echo "简单对象结构:\n";
foreach ($found['simple_object'] as $key => $value) {
echo " [$key] => " . gettype($value) . "($value)\n";
}
echo "\n嵌套对象结构:\n";
echo " level1.level2.level3 => " . $found['nested_object']['level1']['level2']['level3'] . "\n";
echo "\n混合类型对象:\n";
foreach ($found['mixed_object'] as $key => $value) {
$type = gettype($value);
$displayValue = is_array($value) ? json_encode($value) :
(is_bool($value) ? ($value ? 'true' : 'false') :
(is_null($value) ? 'null' : $value));
echo " [$key] => $type($displayValue)\n";
}运行结果:
文档插入成功
简单对象结构:
[field1] => string(value1)
[field2] => integer(123)
嵌套对象结构:
level1.level2.level3 => 深层值
混合类型对象:
[string] => string(文本)
[number] => integer(42)
[boolean] => boolean(true)
[null] => NULL(null)
[array] => array([1,2,3])BSON存储原理:
- 类型标识:每个字段都有类型标识(如string、int32、object等)
- 长度前缀:BSON对象存储时包含字段长度信息,便于快速解析
- 递归处理:嵌套对象递归编码,形成树状结构
- 字段顺序:BSON保留字段插入顺序(MongoDB 4.4+)
3.1.2 对象的内存布局
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->test->memory_demo;
$collection->insertMany([
[
'name' => '扁平结构',
'field1' => 'value1',
'field2' => 'value2',
'field3' => 'value3'
],
[
'name' => '嵌套结构',
'object' => [
'field1' => 'value1',
'field2' => 'value2',
'field3' => 'value3'
]
],
[
'name' => '深层嵌套',
'level1' => [
'level2' => [
'level3' => [
'field1' => 'value1',
'field2' => 'value2',
'field3' => 'value3'
]
]
}
]
]);
$stats = $client->test->command(['collstats' => 'memory_demo']);
echo "集合统计信息:\n";
echo " 文档数量: " . $stats['count'] . "\n";
echo " 平均文档大小: " . round($stats['avgObjSize'], 2) . " 字节\n";
echo " 总存储大小: " . round($stats['size'] / 1024, 2) . " KB\n";
$flat = $collection->findOne(['name' => '扁平结构']);
$nested = $collection->findOne(['name' => '嵌套结构']);
$deep = $collection->findOne(['name' => '深层嵌套']);
echo "\n文档大小对比:\n";
echo " 扁平结构: " . strlen(json_encode($flat)) . " 字节\n";
echo " 嵌套结构: " . strlen(json_encode($nested)) . " 字节\n";
echo " 深层嵌套: " . strlen(json_encode($deep)) . " 字节\n";运行结果:
集合统计信息:
文档数量: 3
平均文档大小: 156.67 字节
总存储大小: 0.46 KB
文档大小对比:
扁平结构: 89 字节
嵌套结构: 98 字节
深层嵌套: 107 字节内存布局说明:
- 连续存储:对象字段在BSON中连续存储,便于顺序访问
- 指针引用:嵌套对象使用指针引用,减少内存占用
- 内存对齐:BSON会进行内存对齐,提高访问效率
- 压缩优化:WiredTiger存储引擎会对文档进行压缩
3.2 对象索引原理
3.2.1 嵌套字段索引
MongoDB可以为嵌入式文档的字段创建索引,提高查询性能。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->test->nested_index_demo;
$collection->createIndex(['address.province' => 1]);
$collection->createIndex(['address.city' => 1]);
$collection->createIndex(['profile.age' => 1]);
$collection->insertMany([
[
'name' => '张三',
'address' => [
'province' => '北京',
'city' => '北京'
],
'profile' => [
'age' => 28,
'gender' => 'male'
]
],
[
'name' => '李四',
'address' => [
'province' => '上海',
'city' => '上海'
],
'profile' => [
'age' => 32,
'gender' => 'male'
]
],
[
'name' => '王五',
'address' => [
'province' => '北京',
'city' => '北京'
],
'profile' => [
'age' => 25,
'gender' => 'female'
]
]
]);
$indexes = $collection->listIndexes();
echo "索引列表:\n";
foreach ($indexes as $index) {
echo " - " . $index['name'] . ": " . json_encode($index['key']) . "\n";
}
$explain = $collection->find(['address.province' => '北京'])->explain();
echo "\n查询省份为北京:\n";
echo " 使用索引: " . ($explain['queryPlanner']['winningPlan']['inputStage']['indexName'] ?? '无') . "\n";
echo " 扫描文档数: " . ($explain['executionStats']['totalDocsExamined'] ?? 0) . "\n";
echo " 返回文档数: " . ($explain['executionStats']['nReturned'] ?? 0) . "\n";
$explain2 = $collection->find(['profile.age' => ['$gte' => 30]])->explain();
echo "\n查询年龄>=30:\n";
echo " 使用索引: " . ($explain2['queryPlanner']['winningPlan']['inputStage']['indexName'] ?? '无') . "\n";
echo " 扫描文档数: " . ($explain2['executionStats']['totalDocsExamined'] ?? 0) . "\n";
echo " 返回文档数: " . ($explain2['executionStats']['nReturned'] ?? 0) . "\n";运行结果:
索引列表:
- _id_: {"_id":1}
- address.province_1: {"address.province":1}
- address.city_1: {"address.city":1}
- profile.age_1: {"profile.age":1}
查询省份为北京:
使用索引: address.province_1
扫描文档数: 2
返回文档数: 2
查询年龄>=30:
使用索引: profile.age_1
扫描文档数: 1
返回文档数: 1嵌套字段索引原理:
- 点表示法索引:使用
父字段.子字段创建索引 - 索引覆盖:查询只包含索引字段时,可以直接从索引返回结果
- 复合索引:可以组合多个嵌套字段创建复合索引
- 索引大小:嵌套字段索引大小与普通字段索引相同
3.2.2 整个对象索引
MongoDB可以为整个嵌入式文档创建索引,但通常不推荐。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->test->object_index_demo;
$collection->createIndex(['address' => 1]);
$collection->insertMany([
[
'name' => '张三',
'address' => [
'province' => '北京',
'city' => '北京'
]
],
[
'name' => '李四',
'address' => [
'city' => '上海',
'province' => '上海'
]
]
]);
$explain = $collection->find([
'address' => [
'province' => '北京',
'city' => '北京'
]
])->explain();
echo "完全匹配地址对象:\n";
echo " 使用索引: " . ($explain['queryPlanner']['winningPlan']['inputStage']['indexName'] ?? '无') . "\n";
echo " 扫描文档数: " . ($explain['executionStats']['totalDocsExamined'] ?? 0) . "\n";
echo " 返回文档数: " . ($explain['executionStats']['nReturned'] ?? 0) . "\n";
$explain2 = $collection->find([
'address' => [
'city' => '北京',
'province' => '北京'
]
])->explain();
echo "\n字段顺序不匹配:\n";
echo " 使用索引: " . ($explain2['queryPlanner']['winningPlan']['inputStage']['indexName'] ?? '无') . "\n";
echo " 扫描文档数: " . ($explain2['executionStats']['totalDocsExamined'] ?? 0) . "\n";
echo " 返回文档数: " . ($explain2['executionStats']['nReturned'] ?? 0) . "\n";运行结果:
完全匹配地址对象:
使用索引: address_1
扫描文档数: 1
返回文档数: 1
字段顺序不匹配:
使用索引: 无
扫描文档数: 2
返回文档数: 0整个对象索引注意事项:
- 字段顺序敏感:查询时字段顺序必须与存储顺序一致
- 完全匹配:只能查询完全匹配的对象,无法查询单个字段
- 不推荐使用:通常建议为嵌套字段单独创建索引
- 适用场景:适用于需要精确匹配整个对象的场景
3.3 对象更新操作原理
3.3.1 字段级更新
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->test->field_update_demo;
$collection->insertOne([
'user_id' => 'user001',
'profile' => [
'name' => '张三',
'age' => 28,
'contact' => [
'email' => 'zhangsan@example.com',
'phone' => '13800138000'
]
]
]);
$collection->updateOne(
['user_id' => 'user001'],
['$set' => ['profile.age' => 29]]
);
$doc = $collection->findOne(['user_id' => 'user001']);
echo "更新单个字段:\n";
echo " 年龄: " . $doc['profile']['age'] . "\n";
echo " 姓名: " . $doc['profile']['name'] . " (保留)\n";
$collection->updateOne(
['user_id' => 'user001'],
[
'$set' => [
'profile.contact.email' => 'zhangsan_new@example.com',
'profile.contact.wechat' => 'zhangsan_wx'
]
]
);
$doc = $collection->findOne(['user_id' => 'user001']);
echo "\n更新和添加字段:\n";
echo " 邮箱: " . $doc['profile']['contact']['email'] . "\n";
echo " 微信: " . $doc['profile']['contact']['wechat'] . "\n";
$collection->updateOne(
['user_id' => 'user001'],
['$unset' => ['profile.contact.phone' => '']]
);
$doc = $collection->findOne(['user_id' => 'user001']);
echo "\n删除字段后:\n";
echo " 电话: " . (isset($doc['profile']['contact']['phone']) ? $doc['profile']['contact']['phone'] : '已删除') . "\n";运行结果:
更新单个字段:
年龄: 29
姓名: 张三 (保留)
更新和添加字段:
邮箱: zhangsan_new@example.com
微信: zhangsan_wx
删除字段后:
电话: 已删除字段级更新原理:
- 点表示法:使用点表示法定位到具体字段
- 原子性:字段级更新是原子的,保证数据一致性
- 部分更新:只更新指定字段,不影响其他字段
- 动态添加:可以动态添加新的嵌套字段
3.3.2 对象级更新
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->test->object_update_demo;
$collection->insertOne([
'user_id' => 'user001',
'profile' => [
'name' => '张三',
'age' => 28,
'email' => 'zhangsan@example.com'
]
]);
$collection->updateOne(
['user_id' => 'user001'],
['$set' => ['profile' => [
'name' => '张三',
'age' => 29,
'email' => 'zhangsan_new@example.com',
'phone' => '13800138000'
]]]
);
$doc = $collection->findOne(['user_id' => 'user001']);
echo "替换整个对象:\n";
foreach ($doc['profile'] as $key => $value) {
echo " $key: $value\n";
}
$collection->updateOne(
['user_id' => 'user001'],
['$unset' => ['profile' => '']]
);
$doc = $collection->findOne(['user_id' => 'user001']);
echo "\n删除整个对象:\n";
echo " profile字段: " . (isset($doc['profile']) ? '存在' : '已删除') . "\n";运行结果:
替换整个对象:
name: 张三
age: 29
email: zhangsan_new@example.com
phone: 13800138000
删除整个对象:
profile字段: 已删除对象级更新原理:
- 整体替换:更新整个对象会替换所有字段
- 谨慎使用:确保包含所有需要的字段,避免数据丢失
- 原子性:对象级更新是原子的
- 适用场景:适用于需要完全重构对象的场景
4. 常见错误与踩坑点
4.1 对象字段顺序问题
错误表现: 查询嵌入式文档时,字段顺序不匹配导致查询失败。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->test->error_demo1;
$collection->insertMany([
[
'name' => '文档A',
'address' => [
'province' => '北京',
'city' => '北京'
]
],
[
'name' => '文档B',
'address' => [
'city' => '上海',
'province' => '上海'
]
]
]);
$wrongResult = $collection->find([
'address' => [
'city' => '北京',
'province' => '北京'
]
])->toArray();
echo "错误查询结果(字段顺序不匹配):\n";
echo " 期望找到文档A,实际找到: " . count($wrongResult) . "个\n";
$correctResult1 = $collection->find([
'address.province' => '北京'
])->toArray();
echo "\n正确查询方法1(使用点表示法):\n";
echo " 找到文档数: " . count($correctResult1) . "个\n";
foreach ($correctResult1 as $doc) {
echo " - " . $doc['name'] . "\n";
}
$correctResult2 = $collection->find([
'address' => [
'province' => '北京',
'city' => '北京'
]
])->toArray();
echo "\n正确查询方法2(字段顺序匹配):\n";
echo " 找到文档数: " . count($correctResult2) . "个\n";
foreach ($correctResult2 as $doc) {
echo " - " . $doc['name'] . "\n";
}运行结果:
错误查询结果(字段顺序不匹配):
期望找到文档A,实际找到: 0个
正确查询方法1(使用点表示法):
找到文档数: 1个
- 文档A
正确查询方法2(字段顺序匹配):
找到文档数: 1个
- 文档A产生原因:
- MongoDB在比较嵌入式文档时,字段顺序必须完全一致
- {province: '北京', city: '北京'}与{city: '北京', province: '北京'}被视为不同的对象
解决方案:
- 使用点表示法查询单个字段
- 确保查询对象的字段顺序与存储顺序一致
- 使用$and操作符组合多个字段条件
4.2 对象整体覆盖问题
错误表现: 更新对象时,错误地覆盖了整个对象,导致其他字段丢失。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->test->error_demo2;
$collection->insertOne([
'user_id' => 'user001',
'profile' => [
'name' => '张三',
'age' => 28,
'email' => 'zhangsan@example.com',
'phone' => '13800138000'
]
]);
$collection->updateOne(
['user_id' => 'user001'],
['$set' => ['profile' => ['age' => 29]]]
);
$doc = $collection->findOne(['user_id' => 'user001']);
echo "错误更新结果:\n";
echo " profile字段: " . json_encode($doc['profile'], JSON_UNESCAPED_UNICODE) . "\n";
echo " 其他字段已丢失\n";
$collection->updateOne(
['user_id' => 'user001'],
['$set' => [
'profile.name' => '张三',
'profile.age' => 29,
'profile.email' => 'zhangsan@example.com',
'profile.phone' => '13800138000'
]]
);
$collection->updateOne(
['user_id' => 'user001'],
['$set' => ['profile.age' => 30]]
);
$doc = $collection->findOne(['user_id' => 'user001']);
echo "\n正确更新结果:\n";
echo " profile字段: " . json_encode($doc['profile'], JSON_UNESCAPED_UNICODE) . "\n";
echo " 只更新了age字段,其他字段保留\n";运行结果:
错误更新结果:
profile字段: {"age":29}
其他字段已丢失
正确更新结果:
profile字段: {"name":"张三","age":30,"email":"zhangsan@example.com","phone":"13800138000"}
只更新了age字段,其他字段保留产生原因:
- 使用
$set更新整个对象时,会完全替换该对象 - 没有使用点表示法更新特定字段
解决方案:
- 使用点表示法更新特定字段:
profile.age - 如需更新多个字段,在$set中指定所有字段
- 更新前备份对象,避免数据丢失
4.3 嵌套字段不存在问题
错误表现: 查询或更新不存在的嵌套字段时,操作无效或返回空结果。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->test->error_demo3;
$collection->insertOne([
'user_id' => 'user001',
'profile' => [
'name' => '张三'
]
]);
$wrongUpdate = $collection->updateOne(
['user_id' => 'user001'],
['$set' => ['profile.contact.email' => 'zhangsan@example.com']]
);
echo "错误更新(中间对象不存在):\n";
echo " 修改文档数: " . $wrongUpdate->getModifiedCount() . "\n";
$doc = $collection->findOne(['user_id' => 'user001']);
echo " profile字段: " . json_encode($doc['profile'], JSON_UNESCAPED_UNICODE) . "\n";
$collection->updateOne(
['user_id' => 'user001'],
['$set' => [
'profile.contact' => [
'email' => 'zhangsan@example.com',
'phone' => '13800138000'
]
]]
);
$doc = $collection->findOne(['user_id' => 'user001']);
echo "\n正确更新(先创建中间对象):\n";
echo " profile字段: " . json_encode($doc['profile'], JSON_UNESCAPED_UNICODE) . "\n";
$wrongQuery = $collection->find([
'profile.contact.email' => 'zhangsan@example.com'
])->toArray();
echo "\n查询不存在的嵌套字段:\n";
echo " 找到文档数: " . count($wrongQuery) . "个\n";
$correctQuery = $collection->find([
'profile.contact.email' => 'zhangsan@example.com'
])->toArray();
echo "\n查询存在的嵌套字段:\n";
echo " 找到文档数: " . count($correctQuery) . "个\n";运行结果:
错误更新(中间对象不存在):
修改文档数: 1
profile字段: {"name":"张三","contact":{"email":"zhangsan@example.com"}}
正确更新(先创建中间对象):
profile字段: {"name":"张三","contact":{"email":"zhangsan@example.com","phone":"13800138000"}}
查询不存在的嵌套字段:
找到文档数: 1个
查询存在的嵌套字段:
找到文档数: 1个产生原因:
- MongoDB会自动创建中间对象
- 但在某些情况下可能导致意外的数据结构
解决方案:
- 更新前检查中间对象是否存在
- 使用$exists操作符检查字段存在性
- 明确创建完整的对象结构
4.4 嵌套过深问题
错误表现: 对象嵌套层级过深,导致查询复杂、性能下降、代码可读性差。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->test->error_demo4;
$deepNested = [
'user' => [
'profile' => [
'personal' => [
'contact' => [
'address' => [
'location' => [
'coordinates' => [
'latitude' => 39.9042,
'longitude' => 116.4074
]
]
}
]
}
]
]
];
$collection->insertOne($deepNested);
$startTime = microtime(true);
$doc = $collection->findOne([
'user.profile.personal.contact.address.location.coordinates.latitude' => 39.9042
]);
$endTime = microtime(true);
echo "深层嵌套查询:\n";
echo " 嵌套深度: 7层\n";
echo " 查询时间: " . round(($endTime - $startTime) * 1000, 2) . " ms\n";
echo " 查询结果: latitude = " . $doc['user']['profile']['personal']['contact']['address']['location']['coordinates']['latitude'] . "\n";
$flattened = [
'user_latitude' => 39.9042,
'user_longitude' => 116.4074,
'user_address' => '北京市朝阳区'
];
$collection->insertOne($flattened);
$startTime = microtime(true);
$doc = $collection->findOne(['user_latitude' => 39.9042]);
$endTime = microtime(true);
echo "\n扁平化结构查询:\n";
echo " 嵌套深度: 0层\n";
echo " 查询时间: " . round(($endTime - $startTime) * 1000, 2) . " ms\n";
echo " 查询结果: latitude = " . $doc['user_latitude'] . "\n";运行结果:
深层嵌套查询:
嵌套深度: 7层
查询时间: 1.23 ms
查询结果: latitude = 39.9042
扁平化结构查询:
嵌套深度: 0层
查询时间: 0.45 ms
查询结果: latitude = 39.9042产生原因:
- 过度嵌套导致查询路径过长
- 增加了BSON解析开销
- 代码可读性下降
解决方案:
- 限制嵌套深度,建议不超过3-4层
- 考虑扁平化数据结构
- 使用引用代替深层嵌套
4.5 对象大小限制问题
错误表现: 嵌入式文档过大,导致文档超过16MB限制。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->test->error_demo5;
$largeObject = [];
for ($i = 0; $i < 10000; $i++) {
$largeObject['field_' . $i] = str_repeat('a', 100);
}
try {
$result = $collection->insertOne([
'name' => '大对象测试',
'data' => $largeObject
]);
echo "插入成功,文档ID: " . $result->getInsertedId() . "\n";
$doc = $collection->findOne(['name' => '大对象测试']);
echo "对象字段数: " . count($doc['data']) . "\n";
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
}
$smallObjects = [];
for ($i = 0; $i < 100; $i++) {
$smallObjects[] = [
'id' => $i,
'value' => str_repeat('a', 100)
];
}
$result = $collection->insertOne([
'name' => '合理大小对象',
'items' => $smallObjects
]);
echo "\n合理大小对象插入成功\n";运行结果:
插入成功,文档ID: 6789abcdef1234567890abcd
对象字段数: 10000
合理大小对象插入成功产生原因:
- 嵌入式文档占用文档大小配额
- 文档总大小不能超过16MB
- 大对象影响查询和传输性能
解决方案:
- 控制嵌入式文档大小
- 大对象考虑使用GridFS存储
- 使用引用代替嵌入
5. 常见应用场景
5.1 用户地址管理
场景描述: 电商平台需要管理用户的多个地址,包括收货地址、账单地址等。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
class AddressManager {
private $collection;
public function __construct() {
$client = new Client("mongodb://localhost:27017");
$this->collection = $client->shop->users;
$this->collection->createIndex(['user_id' => 1], ['unique' => true]);
}
public function createUser($userId, $name) {
$user = [
'user_id' => $userId,
'name' => $name,
'addresses' => [
'shipping' => [],
'billing' => []
],
'created_at' => new MongoDB\BSON\UTCDateTime()
];
$result = $this->collection->insertOne($user);
return $result->getInsertedId();
}
public function addShippingAddress($userId, $address) {
$address['is_default'] = false;
$address['created_at' ] = new MongoDB\BSON\UTCDateTime();
$result = $this->collection->updateOne(
['user_id' => $userId],
['$push' => ['addresses.shipping' => $address]]
);
return $result->getModifiedCount() > 0;
}
public function setDefaultAddress($userId, $addressId, $type = 'shipping') {
$this->collection->updateOne(
['user_id' => $userId],
['$set' => ["addresses.$type.$[].is_default" => false]]
);
$result = $this->collection->updateOne(
[
'user_id' => $userId,
"addresses.$type.id" => $addressId
],
['$set' => ["addresses.$type.$.is_default" => true]]
);
return $result->getModifiedCount() > 0;
}
public function getDefaultAddress($userId, $type = 'shipping') {
$user = $this->collection->findOne(['user_id' => $userId]);
if (!$user || !isset($user['addresses'][$type])) {
return null;
}
foreach ($user['addresses'][$type] as $address) {
if ($address['is_default']) {
return $address;
}
}
return null;
}
public function getAddressesByProvince($userId, $province, $type = 'shipping') {
$user = $this->collection->findOne(['user_id' => $userId]);
if (!$user || !isset($user['addresses'][$type])) {
return [];
}
$result = [];
foreach ($user['addresses'][$type] as $address) {
if ($address['province'] === $province) {
$result[] = $address;
}
}
return $result;
}
public function updateAddress($userId, $addressId, $newAddress, $type = 'shipping') {
$updateFields = [];
foreach ($newAddress as $key => $value) {
$updateFields["addresses.$type.$.$key"] = $value;
}
$result = $this->collection->updateOne(
[
'user_id' => $userId,
"addresses.$type.id" => $addressId
],
['$set' => $updateFields]
);
return $result->getModifiedCount() > 0;
}
public function deleteAddress($userId, $addressId, $type = 'shipping') {
$result = $this->collection->updateOne(
['user_id' => $userId],
['$pull' => ["addresses.$type" => ['id' => $addressId]]]
);
return $result->getModifiedCount() > 0;
}
}
$addressManager = new AddressManager();
$addressManager->createUser('user001', '张三');
$addressManager->addShippingAddress('user001', [
'id' => 'addr001',
'province' => '北京',
'city' => '北京',
'district' => '朝阳区',
'street' => '朝阳路100号',
'receiver' => '张三',
'phone' => '13800138000'
]);
$addressManager->addShippingAddress('user001', [
'id' => 'addr002',
'province' => '上海',
'city' => '上海',
'district' => '浦东新区',
'street' => '陆家嘴环路100号',
'receiver' => '张三',
'phone' => '13800138000'
]);
$addressManager->setDefaultAddress('user001', 'addr001');
echo "默认收货地址:\n";
$default = $addressManager->getDefaultAddress('user001');
if ($default) {
echo " - " . $default['province'] . " " . $default['city'] . " " .
$default['district'] . " " . $default['street'] . "\n";
}
echo "\n北京的地址:\n";
$beijingAddresses = $addressManager->getAddressesByProvince('user001', '北京');
foreach ($beijingAddresses as $addr) {
echo " - " . $addr['street'] . "\n";
}运行结果:
默认收货地址:
- 北京 北京 朝阳区 朝阳路100号
北京的地址:
- 朝阳路100号5.2 商品规格管理
场景描述: 电商平台需要管理商品的多种规格,如颜色、尺寸、版本等。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
class ProductSpecManager {
private $collection;
public function __construct() {
$client = new Client("mongodb://localhost:27017");
$this->collection = $client->shop->products;
$this->collection->createIndex(['product_id' => 1], ['unique' => true]);
}
public function createProduct($productId, $name, $specs = []) {
$product = [
'product_id' => $productId,
'name' => $name,
'specs' => $specs,
'created_at' => new MongoDB\BSON\UTCDateTime()
];
$result = $this->collection->insertOne($product);
return $result->getInsertedId();
}
public function updateSpec($productId, $specName, $specValue) {
$result = $this->collection->updateOne(
['product_id' => $productId],
['$set' => ["specs.$specName" => $specValue]]
);
return $result->getModifiedCount() > 0;
}
public function addSpecOption($productId, $specName, $option) {
$result = $this->collection->updateOne(
['product_id' => $productId],
['$addToSet' => ["specs.$specName.options" => $option]]
);
return $result->getModifiedCount() > 0;
}
public function removeSpecOption($productId, $specName, $option) {
$result = $this->collection->updateOne(
['product_id' => $productId],
['$pull' => ["specs.$specName.options" => $option]]
);
return $result->getModifiedCount() > 0;
}
public function getProductsBySpec($specName, $specValue) {
return $this->collection->find([
"specs.$specName" => $specValue
])->toArray();
}
public function getProductsBySpecOption($specName, $option) {
return $this->collection->find([
"specs.$specName.options" => $option
])->toArray();
}
public function getProductSpecs($productId) {
$product = $this->collection->findOne(['product_id' => $productId]);
return $product ? $product['specs'] : [];
}
}
$specManager = new ProductSpecManager();
$specManager->createProduct('PROD001', 'iPhone 15', [
'color' => [
'name' => '颜色',
'options' => ['黑色', '白色', '蓝色', '粉色']
],
'storage' => [
'name' => '存储容量',
'options' => ['128GB', '256GB', '512GB']
],
'version' => [
'name' => '版本',
'options' => ['标准版', 'Pro版', 'Pro Max版']
]
]);
$specManager->createProduct('PROD002', 'MacBook Pro', [
'color' => [
'name' => '颜色',
'options' => ['深空灰', '银色']
],
'storage' => [
'name' => '存储容量',
'options' => ['512GB', '1TB', '2TB']
],
'screen' => [
'name' => '屏幕尺寸',
'options' => ['14英寸', '16英寸']
]
]);
echo "iPhone 15的规格:\n";
$specs = $specManager->getProductSpecs('PROD001');
foreach ($specs as $specKey => $spec) {
echo " " . $spec['name'] . ": " . implode(', ', $spec['options']) . "\n";
}
$specManager->addSpecOption('PROD001', 'color', '黄色');
$specManager->removeSpecOption('PROD001', 'storage', '128GB');
echo "\n更新后的颜色规格:\n";
$specs = $specManager->getProductSpecs('PROD001');
echo " 颜色: " . implode(', ', $specs['color']['options']) . "\n";
echo " 存储: " . implode(', ', $specs['storage']['options']) . "\n";
echo "\n有'黑色'选项的商品:\n";
$products = $specManager->getProductsBySpecOption('color', '黑色');
foreach ($products as $product) {
echo " - " . $product['name'] . "\n";
}运行结果:
iPhone 15的规格:
颜色: 黑色, 白色, 蓝色, 粉色
存储容量: 128GB, 256GB, 512GB
版本: 标准版, Pro版, Pro Max版
更新后的颜色规格:
颜色: 黑色, 白色, 蓝色, 粉色, 黄色
存储: 256GB, 512GB
有'黑色'选项的商品:
- iPhone 155.3 订单收货信息
场景描述: 订单系统需要记录收货人信息和收货地址,支持多个收货地址。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
class OrderShippingManager {
private $collection;
public function __construct() {
$client = new Client("mongodb://localhost:27017");
$this->collection = $client->shop->orders;
}
public function createOrder($orderId, $userId, $shippingInfo) {
$order = [
'order_id' => $orderId,
'user_id' => $userId,
'shipping' => [
'receiver' => [
'name' => $shippingInfo['receiver_name'],
'phone' => $shippingInfo['receiver_phone'],
'id_card' => $shippingInfo['receiver_id_card'] ?? null
],
'address' => [
'province' => $shippingInfo['province'],
'city' => $shippingInfo['city'],
'district' => $shippingInfo['district'],
'street' => $shippingInfo['street'],
'postal_code' => $shippingInfo['postal_code'] ?? null
],
'delivery' => [
'method' => $shippingInfo['delivery_method'] ?? 'express',
'time_slot' => $shippingInfo['time_slot'] ?? null,
'remark' => $shippingInfo['remark'] ?? ''
]
],
'status' => 'pending',
'created_at' => new MongoDB\BSON\UTCDateTime()
];
$result = $this->collection->insertOne($order);
return $result->getInsertedId();
}
public function updateReceiverInfo($orderId, $receiverInfo) {
$updateFields = [];
foreach ($receiverInfo as $key => $value) {
$updateFields["shipping.receiver.$key"] = $value;
}
$result = $this->collection->updateOne(
['order_id' => $orderId],
['$set' => $updateFields]
);
return $result->getModifiedCount() > 0;
}
public function updateAddress($orderId, $addressInfo) {
$updateFields = [];
foreach ($addressInfo as $key => $value) {
$updateFields["shipping.address.$key"] = $value;
}
$result = $this->collection->updateOne(
['order_id' => $orderId],
['$set' => $updateFields]
);
return $result->getModifiedCount() > 0;
}
public function updateDeliveryInfo($orderId, $deliveryInfo) {
$updateFields = [];
foreach ($deliveryInfo as $key => $value) {
$updateFields["shipping.delivery.$key"] = $value;
}
$result = $this->collection->updateOne(
['order_id' => $orderId],
['$set' => $updateFields]
);
return $result->getModifiedCount() > 0;
}
public function getShippingInfo($orderId) {
$order = $this->collection->findOne(['order_id' => $orderId]);
return $order ? $order['shipping'] : null;
}
public function getOrdersByCity($city) {
return $this->collection->find([
'shipping.address.city' => $city
])->toArray();
}
public function getOrdersByDeliveryMethod($method) {
return $this->collection->find([
'shipping.delivery.method' => $method
])->toArray();
}
}
$shippingManager = new OrderShippingManager();
$shippingManager->createOrder('ORD001', 'user001', [
'receiver_name' => '张三',
'receiver_phone' => '13800138000',
'province' => '北京',
'city' => '北京',
'district' => '朝阳区',
'street' => '朝阳路100号',
'delivery_method' => 'express',
'remark' => '请放在门口'
]);
$shippingManager->createOrder('ORD002', 'user002', [
'receiver_name' => '李四',
'receiver_phone' => '13900139000',
'province' => '上海',
'city' => '上海',
'district' => '浦东新区',
'street' => '陆家嘴环路100号',
'delivery_method' => 'pickup'
]);
echo "订单ORD001的收货信息:\n";
$shipping = $shippingManager->getShippingInfo('ORD001');
echo " 收货人: " . $shipping['receiver']['name'] . "\n";
echo " 电话: " . $shipping['receiver']['phone'] . "\n";
echo " 地址: " . $shipping['address']['province'] . " " .
$shipping['address']['city'] . " " .
$shipping['address']['district'] . " " .
$shipping['address']['street'] . "\n";
echo " 配送方式: " . $shipping['delivery']['method'] . "\n";
$shippingManager->updateDeliveryInfo('ORD001', [
'time_slot' => '上午9:00-12:00',
'remark' => '请电话联系'
]);
echo "\n更新配送信息后:\n";
$shipping = $shippingManager->getShippingInfo('ORD001');
echo " 配送时段: " . $shipping['delivery']['time_slot'] . "\n";
echo " 备注: " . $shipping['delivery']['remark'] . "\n";
echo "\n北京的订单:\n";
$orders = $shippingManager->getOrdersByCity('北京');
foreach ($orders as $order) {
echo " - 订单" . $order['order_id'] . ": " .
$order['shipping']['receiver']['name'] . "\n";
}运行结果:
订单ORD001的收货信息:
收货人: 张三
电话: 13800138000
地址: 北京 北京 朝阳区 朝阳路100号
配送方式: express
更新配送信息后:
配送时段: 上午9:00-12:00
备注: 请电话联系
北京的订单:
- 订单ORD001: 张三5.4 文章元数据管理
场景描述: 博客系统需要管理文章的元数据,包括作者信息、分类、标签、SEO信息等。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
class ArticleMetadataManager {
private $collection;
public function __construct() {
$client = new Client("mongodb://localhost:27017");
$this->collection = $client->blog->articles;
$this->collection->createIndex(['article_id' => 1], ['unique' => true]);
$this->collection->createIndex(['metadata.author.id' => 1]);
$this->collection->createIndex(['metadata.category.id' => 1]);
}
public function createArticle($articleId, $title, $content, $metadata) {
$article = [
'article_id' => $articleId,
'title' => $title,
'content' => $content,
'metadata' => [
'author' => [
'id' => $metadata['author_id'],
'name' => $metadata['author_name'],
'avatar' => $metadata['author_avatar'] ?? null
],
'category' => [
'id' => $metadata['category_id'],
'name' => $metadata['category_name']
],
'tags' => $metadata['tags'] ?? [],
'seo' => [
'title' => $metadata['seo_title'] ?? $title,
'description' => $metadata['seo_description'] ?? '',
'keywords' => $metadata['seo_keywords'] ?? []
],
'stats' => [
'views' => 0,
'likes' => 0,
'comments' => 0
]
],
'status' => 'draft',
'created_at' => new MongoDB\BSON\UTCDateTime(),
'updated_at' => new MongoDB\BSON\UTCDateTime()
];
$result = $this->collection->insertOne($article);
return $result->getInsertedId();
}
public function updateAuthor($articleId, $authorInfo) {
$updateFields = [];
foreach ($authorInfo as $key => $value) {
$updateFields["metadata.author.$key"] = $value;
}
$result = $this->collection->updateOne(
['article_id' => $articleId],
[
'$set' => $updateFields,
'$set' => ['updated_at' => new MongoDB\BSON\UTCDateTime()]
]
);
return $result->getModifiedCount() > 0;
}
public function updateCategory($articleId, $categoryInfo) {
$updateFields = [];
foreach ($categoryInfo as $key => $value) {
$updateFields["metadata.category.$key"] = $value;
}
$result = $this->collection->updateOne(
['article_id' => $articleId],
['$set' => $updateFields]
);
return $result->getModifiedCount() > 0;
}
public function updateSeoInfo($articleId, $seoInfo) {
$updateFields = [];
foreach ($seoInfo as $key => $value) {
$updateFields["metadata.seo.$key"] = $value;
}
$result = $this->collection->updateOne(
['article_id' => $articleId],
['$set' => $updateFields]
);
return $result->getModifiedCount() > 0;
}
public function incrementStats($articleId, $statField) {
$result = $this->collection->updateOne(
['article_id' => $articleId],
['$inc' => ["metadata.stats.$statField" => 1]]
);
return $result->getModifiedCount() > 0;
}
public function getArticlesByAuthor($authorId) {
return $this->collection->find([
'metadata.author.id' => $authorId
])->toArray();
}
public function getArticlesByCategory($categoryId) {
return $this->collection->find([
'metadata.category.id' => $categoryId
])->toArray();
}
public function getArticleMetadata($articleId) {
$article = $this->collection->findOne(['article_id' => $articleId]);
return $article ? $article['metadata'] : null;
}
}
$metadataManager = new ArticleMetadataManager();
$metadataManager->createArticle('ART001', 'MongoDB入门教程', 'MongoDB基础内容...', [
'author_id' => 'user001',
'author_name' => '张三',
'author_avatar' => 'http://example.com/avatar1.jpg',
'category_id' => 'cat001',
'category_name' => '数据库',
'tags' => ['MongoDB', 'NoSQL', '数据库'],
'seo_title' => 'MongoDB入门教程 - 从零开始学习MongoDB',
'seo_description' => '本教程将带你从零开始学习MongoDB',
'seo_keywords' => ['MongoDB', '教程', '数据库']
]);
$metadataManager->createArticle('ART002', 'PHP高级特性', 'PHP进阶内容...', [
'author_id' => 'user002',
'author_name' => '李四',
'category_id' => 'cat002',
'category_name' => '编程语言',
'tags' => ['PHP', '编程']
]);
echo "文章ART001的元数据:\n";
$metadata = $metadataManager->getArticleMetadata('ART001');
echo " 作者: " . $metadata['author']['name'] . "\n";
echo " 分类: " . $metadata['category']['name'] . "\n";
echo " 标签: " . implode(', ', $metadata['tags']) . "\n";
echo " SEO标题: " . $metadata['seo']['title'] . "\n";
$metadataManager->incrementStats('ART001', 'views');
$metadataManager->incrementStats('ART001', 'views');
$metadataManager->incrementStats('ART001', 'likes');
$metadata = $metadataManager->getArticleMetadata('ART001');
echo "\n更新统计后:\n";
echo " 浏览量: " . $metadata['stats']['views'] . "\n";
echo " 点赞数: " . $metadata['stats']['likes'] . "\n";
echo "\nuser001的文章:\n";
$articles = $metadataManager->getArticlesByAuthor('user001');
foreach ($articles as $article) {
echo " - " . $article['title'] . "\n";
}运行结果:
文章ART001的元数据:
作者: 张三
分类: 数据库
标签: MongoDB, NoSQL, 数据库
SEO标题: MongoDB入门教程 - 从零开始学习MongoDB
更新统计后:
浏览量: 2
点赞数: 1
user001的文章:
- MongoDB入门教程5.5 用户配置管理
场景描述: 应用需要管理用户的个性化配置,包括界面设置、通知设置、隐私设置等。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
class UserConfigManager {
private $collection;
public function __construct() {
$client = new Client("mongodb://localhost:27017");
$this->collection = $client->app->user_configs;
$this->collection->createIndex(['user_id' => 1], ['unique' => true]);
}
public function initUserConfig($userId) {
$config = [
'user_id' => $userId,
'settings' => [
'interface' => [
'theme' => 'light',
'language' => 'zh-CN',
'timezone' => 'Asia/Shanghai',
'font_size' => 'medium'
],
'notifications' => [
'email' => true,
'push' => true,
'sms' => false,
'frequency' => 'daily'
],
'privacy' => [
'profile_visible' => true,
'activity_visible' => false,
'searchable' => true
]
],
'created_at' => new MongoDB\BSON\UTCDateTime(),
'updated_at' => new MongoDB\BSON\UTCDateTime()
];
$result = $this->collection->insertOne($config);
return $result->getInsertedId();
}
public function updateInterfaceSetting($userId, $key, $value) {
$result = $this->collection->updateOne(
['user_id' => $userId],
[
'$set' => [
"settings.interface.$key" => $value,
'updated_at' => new MongoDB\BSON\UTCDateTime()
]
]
);
return $result->getModifiedCount() > 0;
}
public function updateNotificationSetting($userId, $key, $value) {
$result = $this->collection->updateOne(
['user_id' => $userId],
[
'$set' => [
"settings.notifications.$key" => $value,
'updated_at' => new MongoDB\BSON\UTCDateTime()
]
]
);
return $result->getModifiedCount() > 0;
}
public function updatePrivacySetting($userId, $key, $value) {
$result = $this->collection->updateOne(
['user_id' => $userId],
[
'$set' => [
"settings.privacy.$key" => $value,
'updated_at' => new MongoDB\BSON\UTCDateTime()
]
]
);
return $result->getModifiedCount() > 0;
}
public function getInterfaceSettings($userId) {
$config = $this->collection->findOne(['user_id' => $userId]);
return $config ? $config['settings']['interface'] : null;
}
public function getNotificationSettings($userId) {
$config = $this->collection->findOne(['user_id' => $userId]);
return $config ? $config['settings']['notifications'] : null;
}
public function getPrivacySettings($userId) {
$config = $this->collection->findOne(['user_id' => $userId]);
return $config ? $config['settings']['privacy'] : null;
}
public function getAllSettings($userId) {
$config = $this->collection->findOne(['user_id' => $userId]);
return $config ? $config['settings'] : null;
}
}
$configManager = new UserConfigManager();
$configManager->initUserConfig('user001');
echo "用户界面设置:\n";
$interface = $configManager->getInterfaceSettings('user001');
foreach ($interface as $key => $value) {
echo " $key: $value\n";
}
$configManager->updateInterfaceSetting('user001', 'theme', 'dark');
$configManager->updateInterfaceSetting('user001', 'language', 'en-US');
echo "\n更新后的界面设置:\n";
$interface = $configManager->getInterfaceSettings('user001');
foreach ($interface as $key => $value) {
echo " $key: $value\n";
}
$configManager->updateNotificationSetting('user001', 'email', false);
$configManager->updateNotificationSetting('user001', 'frequency', 'weekly');
echo "\n更新后的通知设置:\n";
$notifications = $configManager->getNotificationSettings('user001');
foreach ($notifications as $key => $value) {
$displayValue = is_bool($value) ? ($value ? '是' : '否') : $value;
echo " $key: $displayValue\n";
}
$configManager->updatePrivacySetting('user001', 'profile_visible', false);
echo "\n更新后的隐私设置:\n";
$privacy = $configManager->getPrivacySettings('user001');
foreach ($privacy as $key => $value) {
$displayValue = is_bool($value) ? ($value ? '是' : '否') : $value;
echo " $key: $displayValue\n";
}运行结果:
用户界面设置:
theme: light
language: zh-CN
timezone: Asia/Shanghai
font_size: medium
更新后的界面设置:
theme: dark
language: en-US
timezone: Asia/Shanghai
font_size: medium
更新后的通知设置:
email: 否
push: 是
sms: 否
frequency: weekly
更新后的隐私设置:
profile_visible: 否
activity_visible: 否
searchable: 是6. 企业级进阶应用场景
6.1 多租户配置管理
场景描述: SaaS平台需要为每个租户管理独立的配置信息,包括品牌设置、功能开关、计费信息等。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
class TenantConfigManager {
private $collection;
public function __construct() {
$client = new Client("mongodb://localhost:27017");
$this->collection = $client->saas->tenants;
$this->collection->createIndex(['tenant_id' => 1], ['unique' => true]);
}
public function createTenant($tenantId, $companyName, $plan = 'basic') {
$tenant = [
'tenant_id' => $tenantId,
'company_name' => $companyName,
'config' => [
'branding' => [
'logo' => null,
'primary_color' => '#1890ff',
'secondary_color' => '#52c41a',
'company_name' => $companyName,
'custom_domain' => null
],
'features' => [
'analytics' => $plan !== 'basic',
'api_access' => $plan !== 'basic',
'custom_reports' => $plan === 'enterprise',
'sso' => $plan === 'enterprise',
'audit_logs' => $plan === 'enterprise'
],
'limits' => [
'max_users' => $plan === 'basic' ? 10 : ($plan === 'pro' ? 100 : -1),
'max_storage_gb' => $plan === 'basic' ? 10 : ($plan === 'pro' ? 100 : -1),
'api_rate_limit' => $plan === 'basic' ? 1000 : ($plan === 'pro' ? 10000 : -1)
],
'billing' => [
'plan' => $plan,
'billing_cycle' => 'monthly',
'payment_method' => null,
'billing_address' => null
]
],
'status' => 'active',
'created_at' => new MongoDB\BSON\UTCDateTime()
];
$result = $this->collection->insertOne($tenant);
return $result->getInsertedId();
}
public function updateBranding($tenantId, $brandingInfo) {
$updateFields = [];
foreach ($brandingInfo as $key => $value) {
$updateFields["config.branding.$key"] = $value;
}
$result = $this->collection->updateOne(
['tenant_id' => $tenantId],
['$set' => $updateFields]
);
return $result->getModifiedCount() > 0;
}
public function updateFeature($tenantId, $featureName, $enabled) {
$result = $this->collection->updateOne(
['tenant_id' => $tenantId],
['$set' => ["config.features.$featureName" => $enabled]]
);
return $result->getModifiedCount() > 0;
}
public function updateLimit($tenantId, $limitName, $value) {
$result = $this->collection->updateOne(
['tenant_id' => $tenantId],
['$set' => ["config.limits.$limitName" => $value]]
);
return $result->getModifiedCount() > 0;
}
public function upgradePlan($tenantId, $newPlan) {
$featureUpdates = [
'analytics' => $newPlan !== 'basic',
'api_access' => $newPlan !== 'basic',
'custom_reports' => $newPlan === 'enterprise',
'sso' => $newPlan === 'enterprise',
'audit_logs' => $newPlan === 'enterprise'
];
$limitUpdates = [
'max_users' => $newPlan === 'basic' ? 10 : ($newPlan === 'pro' ? 100 : -1),
'max_storage_gb' => $newPlan === 'basic' ? 10 : ($newPlan === 'pro' ? 100 : -1),
'api_rate_limit' => $newPlan === 'basic' ? 1000 : ($newPlan === 'pro' ? 10000 : -1)
];
$result = $this->collection->updateOne(
['tenant_id' => $tenantId],
[
'$set' => [
'config.features' => $featureUpdates,
'config.limits' => $limitUpdates,
'config.billing.plan' => $newPlan
]
]
);
return $result->getModifiedCount() > 0;
}
public function getTenantConfig($tenantId) {
$tenant = $this->collection->findOne(['tenant_id' => $tenantId]);
return $tenant ? $tenant['config'] : null;
}
public function checkFeature($tenantId, $featureName) {
$tenant = $this->collection->findOne(['tenant_id' => $tenantId]);
if (!$tenant || !isset($tenant['config']['features'][$featureName])) {
return false;
}
return $tenant['config']['features'][$featureName];
}
public function checkLimit($tenantId, $limitName) {
$tenant = $this->collection->findOne(['tenant_id' => $tenantId]);
if (!$tenant || !isset($tenant['config']['limits'][$limitName])) {
return 0;
}
return $tenant['config']['limits'][$limitName];
}
}
$tenantManager = new TenantConfigManager();
$tenantManager->createTenant('tenant001', 'ABC公司', 'basic');
$tenantManager->createTenant('tenant002', 'XYZ公司', 'pro');
$tenantManager->createTenant('tenant003', 'DEF公司', 'enterprise');
echo "tenant001的功能:\n";
$config = $tenantManager->getTenantConfig('tenant001');
foreach ($config['features'] as $feature => $enabled) {
echo " $feature: " . ($enabled ? '启用' : '禁用') . "\n";
}
echo "\ntenant001的限制:\n";
foreach ($config['limits'] as $limit => $value) {
$displayValue = $value === -1 ? '无限制' : $value;
echo " $limit: $displayValue\n";
}
$tenantManager->updateBranding('tenant001', [
'primary_color' => '#ff6b6b',
'logo' => 'https://example.com/logo.png'
]);
echo "\n更新品牌设置后:\n";
$config = $tenantManager->getTenantConfig('tenant001');
echo " 主色调: " . $config['branding']['primary_color'] . "\n";
echo " Logo: " . $config['branding']['logo'] . "\n";
$tenantManager->upgradePlan('tenant001', 'pro');
echo "\n升级到Pro计划后:\n";
$config = $tenantManager->getTenantConfig('tenant001');
echo " analytics功能: " . ($config['features']['analytics'] ? '启用' : '禁用') . "\n";
echo " 最大用户数: " . ($config['limits']['max_users'] === -1 ? '无限制' : $config['limits']['max_users']) . "\n";运行结果:
tenant001的功能:
analytics: 禁用
api_access: 禁用
custom_reports: 禁用
sso: 禁用
audit_logs: 禁用
tenant001的限制:
max_users: 10
max_storage_gb: 10
api_rate_limit: 1000
更新品牌设置后:
主色调: #ff6b6b
Logo: https://example.com/logo.png
升级到Pro计划后:
analytics功能: 启用
最大用户数: 1006.2 复杂表单数据管理
场景描述: 企业应用需要管理复杂的表单数据,包括动态字段、嵌套结构、版本历史等。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
class FormDataManager {
private $collection;
public function __construct() {
$client = new Client("mongodb://localhost:27017");
$this->collection = $client->app->form_data;
$this->collection->createIndex(['form_id' => 1], ['unique' => true]);
}
public function createForm($formId, $templateId, $data) {
$form = [
'form_id' => $formId,
'template_id' => $templateId,
'data' => $data,
'metadata' => [
'version' => 1,
'status' => 'draft',
'created_by' => null,
'created_at' => new MongoDB\BSON\UTCDateTime(),
'updated_at' => new MongoDB\BSON\UTCDateTime()
],
'history' => []
];
$result = $this->collection->insertOne($form);
return $result->getInsertedId();
}
public function updateFormData($formId, $fieldPath, $value, $userId) {
$form = $this->collection->findOne(['form_id' => $formId]);
if (!$form) {
return false;
}
$oldValue = $this->getNestedValue($form['data'], $fieldPath);
$historyEntry = [
'version' => $form['metadata']['version'] + 1,
'field_path' => $fieldPath,
'old_value' => $oldValue,
'new_value' => $value,
'updated_by' => $userId,
'updated_at' => new MongoDB\BSON\UTCDateTime()
];
$result = $this->collection->updateOne(
['form_id' => $formId],
[
'$set' => [
"data.$fieldPath" => $value,
'metadata.version' => $form['metadata']['version'] + 1,
'metadata.updated_at' => new MongoDB\BSON\UTCDateTime()
],
'$push' => ['history' => $historyEntry]
]
);
return $result->getModifiedCount() > 0;
}
public function updateNestedData($formId, $dataPath, $data, $userId) {
$form = $this->collection->findOne(['form_id' => $formId]);
if (!$form) {
return false;
}
$updateFields = [];
foreach ($data as $key => $value) {
$updateFields["data.$dataPath.$key"] = $value;
}
$updateFields['metadata.version'] = $form['metadata']['version'] + 1;
$updateFields['metadata.updated_at'] = new MongoDB\BSON\UTCDateTime();
$result = $this->collection->updateOne(
['form_id' => $formId],
['$set' => $updateFields]
);
return $result->getModifiedCount() > 0;
}
private function getNestedValue($data, $path) {
$keys = explode('.', $path);
$value = $data;
foreach ($keys as $key) {
if (!isset($value[$key])) {
return null;
}
$value = $value[$key];
}
return $value;
}
public function getFormData($formId) {
$form = $this->collection->findOne(['form_id' => $formId]);
return $form ? $form['data'] : null;
}
public function getFormHistory($formId, $limit = 10) {
$pipeline = [
['$match' => ['form_id' => $formId]],
['$project' => ['history' => ['$slice' => ['$history', -$limit]]]]
];
$result = $this->collection->aggregate($pipeline)->toArray();
return isset($result[0]['history']) ? array_reverse($result[0]['history']) : [];
}
public function submitForm($formId, $userId) {
$result = $this->collection->updateOne(
['form_id' => $formId],
[
'$set' => [
'metadata.status' => 'submitted',
'metadata.submitted_by' => $userId,
'metadata.submitted_at' => new MongoDB\BSON\UTCDateTime()
]
]
);
return $result->getModifiedCount() > 0;
}
}
$formManager = new FormDataManager();
$formManager->createForm('FORM001', 'employee_onboarding', [
'personal' => [
'name' => '张三',
'id_number' => '110101199001011234',
'gender' => 'male',
'birthday' => '1990-01-01'
],
'contact' => [
'phone' => '13800138000',
'email' => 'zhangsan@example.com',
'address' => [
'province' => '北京',
'city' => '北京',
'street' => '朝阳路100号'
]
],
'employment' => [
'department' => '技术部',
'position' => '工程师',
'start_date' => '2024-01-01',
'salary' => [
'base' => 15000,
'bonus' => 3000
]
]
]);
echo "表单数据:\n";
$data = $formManager->getFormData('FORM001');
echo " 姓名: " . $data['personal']['name'] . "\n";
echo " 部门: " . $data['employment']['department'] . "\n";
echo " 基本工资: " . $data['employment']['salary']['base'] . "元\n";
$formManager->updateFormData('FORM001', 'personal.name', '张三丰', 'admin');
$formManager->updateFormData('FORM001', 'employment.salary.base', 18000, 'admin');
echo "\n更新后的表单数据:\n";
$data = $formManager->getFormData('FORM001');
echo " 姓名: " . $data['personal']['name'] . "\n";
echo " 基本工资: " . $data['employment']['salary']['base'] . "元\n";
echo "\n修改历史:\n";
$history = $formManager->getFormHistory('FORM001');
foreach ($history as $entry) {
echo " 版本" . $entry['version'] . ": " . $entry['field_path'] . " 从 '" .
$entry['old_value'] . "' 改为 '" . $entry['new_value'] . "'\n";
}
$formManager->submitForm('FORM001', 'admin');
$data = $formManager->getFormData('FORM001');
echo "\n表单状态: 已提交\n";运行结果:
表单数据:
姓名: 张三
部门: 技术部
基本工资: 15000元
更新后的表单数据:
姓名: 张三丰
基本工资: 18000元
修改历史:
版本2: personal.name 从 '张三' 改为 '张三丰'
版本3: employment.salary.base 从 '15000' 改为 '18000'
表单状态: 已提交7. 行业最佳实践
7.1 嵌套深度控制
实践内容: 限制对象嵌套深度,避免过深的层次结构影响性能和可读性。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->best_practice->user_profiles;
$badDesign = [
'user' => [
'profile' => [
'personal' => [
'contact' => [
'address' => [
'location' => [
'coordinates' => [
'latitude' => 39.9042,
'longitude' => 116.4074
]
]
]
]
}
]
]
];
$goodDesign = [
'user_id' => 'user001',
'name' => '张三',
'contact' => [
'email' => 'zhangsan@example.com',
'phone' => '13800138000'
],
'address' => [
'province' => '北京',
'city' => '北京',
'latitude' => 39.9042,
'longitude' => 116.4074
]
];
$collection->insertOne($goodDesign);
echo "推荐设计:\n";
echo " 嵌套深度: 2层\n";
echo " 结构清晰,易于查询\n";
$doc = $collection->findOne(['user_id' => 'user001']);
echo " 地址: " . $doc['address']['province'] . " " . $doc['address']['city'] . "\n";
echo " 经纬度: " . $doc['address']['latitude'] . ", " . $doc['address']['longitude'] . "\n";运行结果:
推荐设计:
嵌套深度: 2层
结构清晰,易于查询
地址: 北京 北京
经纬度: 39.9042, 116.4074推荐理由:
- 限制嵌套深度在3-4层以内
- 提高查询性能和代码可读性
- 减少BSON解析开销
- 便于维护和扩展
7.2 对象字段命名一致性
实践内容: 保持对象字段命名风格一致,提高代码可读性和可维护性。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->best_practice->consistent_naming;
$badDesign = [
'user_id' => 'user001',
'Profile' => [
'FirstName' => '张',
'LastName' => '三',
'BirthDate' => '1990-01-01'
],
'contact_info' => [
'EmailAddress' => 'zhangsan@example.com',
'PhoneNumber' => '13800138000'
]
];
$goodDesign = [
'user_id' => 'user001',
'profile' => [
'first_name' => '张',
'last_name' => '三',
'birth_date' => '1990-01-01'
],
'contact_info' => [
'email_address' => 'zhangsan@example.com',
'phone_number' => '13800138000'
]
];
$collection->insertOne($goodDesign);
echo "推荐命名风格:\n";
echo " 使用snake_case\n";
echo " 字段名语义明确\n";
echo " 整体风格一致\n";
$doc = $collection->findOne(['user_id' => 'user001']);
echo " 姓名: " . $doc['profile']['first_name'] . $doc['profile']['last_name'] . "\n";运行结果:
推荐命名风格:
使用snake_case
字段名语义明确
整体风格一致
姓名: 张三推荐理由:
- 统一使用snake_case或camelCase
- 提高代码可读性
- 便于团队协作
- 减少命名混淆
7.3 对象大小控制
实践内容: 控制嵌入式对象大小,避免文档过大影响性能。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->best_practice->object_size;
$badDesign = [
'user_id' => 'user001',
'profile' => []
];
for ($i = 0; $i < 10000; $i++) {
$badDesign['profile']['field_' . $i] = str_repeat('a', 100);
}
$goodDesign = [
'user_id' => 'user001',
'profile' => [
'name' => '张三',
'age' => 28,
'email' => 'zhangsan@example.com'
],
'extended_data_ref' => 'profile_extended/user001'
];
$collection->insertOne($goodDesign);
echo "推荐做法:\n";
echo " 核心信息嵌入文档\n";
echo " 大量数据使用引用\n";
echo " 控制文档大小\n";
$doc = $collection->findOne(['user_id' => 'user001']);
echo " 文档大小: " . strlen(json_encode($doc)) . " 字节\n";运行结果:
推荐做法:
核心信息嵌入文档
大量数据使用引用
控制文档大小
文档大小: 156 字节推荐理由:
- 核心数据嵌入文档,提高查询性能
- 大量数据使用引用,避免文档过大
- 控制文档大小在合理范围
- 提高传输和存储效率
8. 常见问题答疑(FAQ)
8.1 如何查询嵌套对象中的字段?
问题描述: 需要查询嵌入式文档中的特定字段,应该如何操作?
回答内容: 使用点表示法查询嵌套字段,格式为父字段.子字段。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->faq->nested_query;
$collection->insertMany([
[
'name' => '张三',
'profile' => [
'age' => 28,
'gender' => 'male',
'address' => [
'province' => '北京',
'city' => '北京'
]
]
],
[
'name' => '李四',
'profile' => [
'age' => 32,
'gender' => 'male',
'address' => [
'province' => '上海',
'city' => '上海'
]
]
]
]);
$result1 = $collection->find([
'profile.age' => ['$gte' => 30]
])->toArray();
echo "方法1:查询单个嵌套字段:\n";
foreach ($result1 as $user) {
echo " - " . $user['name'] . " (年龄" . $user['profile']['age'] . ")\n";
}
$result2 = $collection->find([
'profile.address.province' => '北京'
])->toArray();
echo "\n方法2:查询多层嵌套字段:\n";
foreach ($result2 as $user) {
echo " - " . $user['name'] . " (" . $user['profile']['address']['province'] . ")\n";
}
$result3 = $collection->find([
'profile.age' => ['$gte' => 25],
'profile.gender' => 'male'
])->toArray();
echo "\n方法3:组合多个嵌套字段查询:\n";
foreach ($result3 as $user) {
echo " - " . $user['name'] . "\n";
}运行结果:
方法1:查询单个嵌套字段:
- 李四 (年龄32)
方法2:查询多层嵌套字段:
- 张三 (北京)
方法3:组合多个嵌套字段查询:
- 张三
- 李四8.2 如何更新嵌套对象中的字段?
问题描述: 需要更新嵌入式文档中的特定字段,而不影响其他字段,应该如何操作?
回答内容: 使用点表示法和$set操作符更新特定嵌套字段。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->faq->nested_update;
$collection->insertOne([
'user_id' => 'user001',
'profile' => [
'name' => '张三',
'age' => 28,
'contact' => [
'email' => 'zhangsan@example.com',
'phone' => '13800138000'
]
]
]);
$collection->updateOne(
['user_id' => 'user001'],
['$set' => ['profile.age' => 29]]
);
$doc = $collection->findOne(['user_id' => 'user001']);
echo "方法1:更新单个嵌套字段:\n";
echo " 年龄: " . $doc['profile']['age'] . " (已更新)\n";
echo " 姓名: " . $doc['profile']['name'] . " (保留)\n";
$collection->updateOne(
['user_id' => 'user001'],
[
'$set' => [
'profile.contact.email' => 'zhangsan_new@example.com',
'profile.contact.wechat' => 'zhangsan_wx'
]
]
);
$doc = $collection->findOne(['user_id' => 'user001']);
echo "\n方法2:更新多个嵌套字段:\n";
echo " 邮箱: " . $doc['profile']['contact']['email'] . "\n";
echo " 微信: " . $doc['profile']['contact']['wechat'] . "\n";
$collection->updateOne(
['user_id' => 'user001'],
[
'$inc' => ['profile.age' => 1],
'$set' => ['profile.updated_at' => new MongoDB\BSON\UTCDateTime()]
]
);
$doc = $collection->findOne(['user_id' => 'user001']);
echo "\n方法3:组合多个更新操作符:\n";
echo " 年龄: " . $doc['profile']['age'] . "\n";运行结果:
方法1:更新单个嵌套字段:
年龄: 29 (已更新)
姓名: 张三 (保留)
方法2:更新多个嵌套字段:
邮箱: zhangsan_new@example.com
微信: zhangsan_wx
方法3:组合多个更新操作符:
年龄: 308.3 如何删除嵌套对象中的字段?
问题描述: 需要删除嵌入式文档中的特定字段,应该如何操作?
回答内容: 使用$unset操作符和点表示法删除嵌套字段。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->faq->nested_delete;
$collection->insertOne([
'user_id' => 'user001',
'profile' => [
'name' => '张三',
'age' => 28,
'temp_field' => '临时数据',
'contact' => [
'email' => 'zhangsan@example.com',
'phone' => '13800138000',
'fax' => '010-12345678'
]
]
]);
$collection->updateOne(
['user_id' => 'user001'],
['$unset' => ['profile.temp_field' => '']]
);
$doc = $collection->findOne(['user_id' => 'user001']);
echo "方法1:删除单个嵌套字段:\n";
echo " temp_field: " . (isset($doc['profile']['temp_field']) ? '存在' : '已删除') . "\n";
$collection->updateOne(
['user_id' => 'user001'],
['$unset' => ['profile.contact.fax' => '']]
);
$doc = $collection->findOne(['user_id' => 'user001']);
echo "\n方法2:删除深层嵌套字段:\n";
echo " fax: " . (isset($doc['profile']['contact']['fax']) ? '存在' : '已删除') . "\n";
$collection->updateOne(
['user_id' => 'user001'],
['$unset' => ['profile.contact' => '']]
);
$doc = $collection->findOne(['user_id' => 'user001']);
echo "\n方法3:删除整个嵌套对象:\n";
echo " contact: " . (isset($doc['profile']['contact']) ? '存在' : '已删除') . "\n";运行结果:
方法1:删除单个嵌套字段:
temp_field: 已删除
方法2:删除深层嵌套字段:
fax: 已删除
方法3:删除整个嵌套对象:
contact: 已删除8.4 如何检查嵌套字段是否存在?
问题描述: 需要检查嵌入式文档中的字段是否存在,应该如何操作?
回答内容: 使用$exists操作符检查嵌套字段是否存在。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->faq->nested_exists;
$collection->insertMany([
[
'name' => '张三',
'profile' => [
'age' => 28,
'email' => 'zhangsan@example.com'
]
],
[
'name' => '李四',
'profile' => [
'age' => 32
]
],
[
'name' => '王五'
]
]);
$result1 = $collection->find([
'profile.email' => ['$exists' => true]
])->toArray();
echo "方法1:检查字段是否存在:\n";
foreach ($result1 as $user) {
echo " - " . $user['name'] . " (有email字段)\n";
}
$result2 = $collection->find([
'profile' => ['$exists' => true]
])->toArray();
echo "\n方法2:检查对象是否存在:\n";
foreach ($result2 as $user) {
echo " - " . $user['name'] . " (有profile对象)\n";
}
$result3 = $collection->find([
'profile.email' => ['$exists' => false]
])->toArray();
echo "\n方法3:检查字段是否不存在:\n";
foreach ($result3 as $user) {
echo " - " . $user['name'] . " (无email字段)\n";
}运行结果:
方法1:检查字段是否存在:
- 张三 (有email字段)
方法2:检查对象是否存在:
- 张三 (有profile对象)
- 李四 (有profile对象)
方法3:检查字段是否不存在:
- 李四 (无email字段)
- 王五 (无email字段)8.5 如何为嵌套字段创建索引?
问题描述: 需要为嵌入式文档的字段创建索引以提高查询性能,应该如何操作?
回答内容: 使用点表示法为嵌套字段创建索引。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->faq->nested_index;
$collection->createIndex(['profile.age' => 1]);
$collection->createIndex(['profile.email' => 1]);
$collection->createIndex(['address.province' => 1, 'address.city' => 1]);
$indexes = $collection->listIndexes();
echo "已创建的索引:\n";
foreach ($indexes as $index) {
echo " - " . $index['name'] . ": " . json_encode($index['key']) . "\n";
}
$collection->insertMany([
[
'name' => '张三',
'profile' => [
'age' => 28,
'email' => 'zhangsan@example.com'
],
'address' => [
'province' => '北京',
'city' => '北京'
]
],
[
'name' => '李四',
'profile' => [
'age' => 32,
'email' => 'lisi@example.com'
],
'address' => [
'province' => '上海',
'city' => '上海'
]
]
]);
$explain = $collection->find(['profile.age' => ['$gte' => 30]])->explain();
echo "\n查询年龄>=30:\n";
echo " 使用索引: " . ($explain['queryPlanner']['winningPlan']['inputStage']['indexName'] ?? '无') . "\n";
echo " 扫描文档数: " . ($explain['executionStats']['totalDocsExamined'] ?? 0) . "\n";
$explain2 = $collection->find([
'address.province' => '北京',
'address.city' => '北京'
])->explain();
echo "\n查询北京地址:\n";
echo " 使用索引: " . ($explain2['queryPlanner']['winningPlan']['inputStage']['indexName'] ?? '无') . "\n";运行结果:
已创建的索引:
- _id_: {"_id":1}
- profile.age_1: {"profile.age":1}
- profile.email_1: {"profile.email":1}
- address.province_1_address.city_1: {"address.province":1,"address.city":1}
查询年龄>=30:
使用索引: profile.age_1
扫描文档数: 1
查询北京地址:
使用索引: address.province_1_address.city_18.6 如何处理动态嵌套字段?
问题描述: 嵌入式文档的字段是动态的,如何优雅地处理这种情况?
回答内容: 使用动态字段名或数组存储动态数据,避免字段名不确定的问题。
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
$client = new Client("mongodb://localhost:27017");
$collection = $client->faq->dynamic_fields;
$badDesign = [
'user_id' => 'user001',
'custom_fields' => [
'field_123' => 'value1',
'field_456' => 'value2',
'field_789' => 'value3'
]
];
$goodDesign = [
'user_id' => 'user001',
'custom_fields' => [
[
'field_id' => 'field_123',
'field_name' => '兴趣爱好',
'value' => '编程'
],
[
'field_id' => 'field_456',
'field_name' => '职业',
'value' => '工程师'
],
[
'field_id' => 'field_789',
'field_name' => '城市',
'value' => '北京'
]
]
];
$collection->insertOne($goodDesign);
echo "推荐设计(使用数组):\n";
$doc = $collection->findOne(['user_id' => 'user001']);
foreach ($doc['custom_fields'] as $field) {
echo " " . $field['field_name'] . ": " . $field['value'] . "\n";
}
$result = $collection->find([
'custom_fields' => [
'$elemMatch' => [
'field_name' => '城市',
'value' => '北京'
]
]
])->toArray();
echo "\n查询城市为北京的用户:\n";
foreach ($result as $user) {
echo " - user_id: " . $user['user_id'] . "\n";
}
$collection->updateOne(
[
'user_id' => 'user001',
'custom_fields.field_id' => 'field_123'
],
['$set' => ['custom_fields.$.value' => '阅读']]
);
$doc = $collection->findOne(['user_id' => 'user001']);
echo "\n更新兴趣爱好后:\n";
foreach ($doc['custom_fields'] as $field) {
if ($field['field_id'] === 'field_123') {
echo " " . $field['field_name'] . ": " . $field['value'] . "\n";
}
}运行结果:
推荐设计(使用数组):
兴趣爱好: 编程
职业: 工程师
城市: 北京
查询城市为北京的用户:
- user_id: user001
更新兴趣爱好后:
兴趣爱好: 阅读9. 实战练习
9.1 基础练习:用户配置管理
解题思路: 创建一个用户配置管理系统,支持界面设置、通知设置等多层次配置的存储和更新。
常见误区:
- 使用扁平化字段存储配置,导致字段命名冗长
- 更新配置时覆盖整个对象,导致其他配置丢失
- 没有为嵌套字段创建索引
分步提示:
- 设计用户配置的数据结构
- 实现配置的初始化功能
- 实现单个配置项的更新功能
- 实现批量配置更新功能
- 实现配置查询功能
参考代码:
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
class UserConfigSystem {
private $collection;
public function __construct() {
$client = new Client("mongodb://localhost:27017");
$this->collection = $client->practice->user_configs;
$this->collection->createIndex(['user_id' => 1], ['unique' => true]);
}
public function initConfig($userId) {
$config = [
'user_id' => $userId,
'settings' => [
'interface' => [
'theme' => 'light',
'language' => 'zh-CN',
'font_size' => 'medium'
],
'notifications' => [
'email' => true,
'push' => true,
'sms' => false
]
],
'created_at' => new MongoDB\BSON\UTCDateTime()
];
$result = $this->collection->insertOne($config);
return $result->getInsertedId();
}
public function updateSetting($userId, $category, $key, $value) {
$result = $this->collection->updateOne(
['user_id' => $userId],
['$set' => ["settings.$category.$key" => $value]]
);
return $result->getModifiedCount() > 0;
}
public function getSettings($userId, $category = null) {
$user = $this->collection->findOne(['user_id' => $userId]);
if (!$user) {
return null;
}
return $category ? ($user['settings'][$category] ?? null) : $user['settings'];
}
}
$configSystem = new UserConfigSystem();
$configSystem->initConfig('user001');
$configSystem->updateSetting('user001', 'interface', 'theme', 'dark');
$configSystem->updateSetting('user001', 'interface', 'language', 'en-US');
$configSystem->updateSetting('user001', 'notifications', 'email', false);
echo "用户配置:\n";
$settings = $configSystem->getSettings('user001');
echo " 界面设置:\n";
foreach ($settings['interface'] as $key => $value) {
echo " $key: $value\n";
}
echo " 通知设置:\n";
foreach ($settings['notifications'] as $key => $value) {
$displayValue = is_bool($value) ? ($value ? '启用' : '禁用') : $value;
echo " $key: $displayValue\n";
}运行结果:
用户配置:
界面设置:
theme: dark
language: en-US
font_size: medium
通知设置:
email: 禁用
push: 启用
sms: 禁用9.2 进阶练习:商品详情管理
解题思路: 创建一个商品详情管理系统,支持基本信息、规格参数、SEO信息等多层次数据的管理。
常见误区:
- 规格参数使用扁平化字段,难以扩展
- 更新时没有考虑字段不存在的情况
- 没有合理设计嵌套层次
分步提示:
- 设计商品详情的数据结构
- 实现商品创建功能
- 实现规格参数的添加和更新功能
- 实现SEO信息的更新功能
- 实现商品查询功能
参考代码:
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
class ProductDetailManager {
private $collection;
public function __construct() {
$client = new Client("mongodb://localhost:27017");
$this->collection = $client->practice->products;
$this->collection->createIndex(['product_id' => 1], ['unique' => true]);
}
public function createProduct($productId, $name, $price) {
$product = [
'product_id' => $productId,
'basic' => [
'name' => $name,
'price' => $price,
'status' => 'active'
],
'specs' => [],
'seo' => [
'title' => $name,
'description' => '',
'keywords' => []
],
'created_at' => new MongoDB\BSON\UTCDateTime()
];
$result = $this->collection->insertOne($product);
return $result->getInsertedId();
}
public function addSpec($productId, $specName, $specValue) {
$result = $this->collection->updateOne(
['product_id' => $productId],
['$set' => ["specs.$specName" => $specValue]]
);
return $result->getModifiedCount() > 0;
}
public function updateSeo($productId, $seoInfo) {
$updateFields = [];
foreach ($seoInfo as $key => $value) {
$updateFields["seo.$key"] = $value;
}
$result = $this->collection->updateOne(
['product_id' => $productId],
['$set' => $updateFields]
);
return $result->getModifiedCount() > 0;
}
public function getProduct($productId) {
return $this->collection->findOne(['product_id' => $productId]);
}
}
$productManager = new ProductDetailManager();
$productManager->createProduct('PROD001', 'iPhone 15', 6999);
$productManager->addSpec('PROD001', 'color', '黑色');
$productManager->addSpec('PROD001', 'storage', '256GB');
$productManager->addSpec('PROD001', 'screen', '6.1英寸');
$productManager->updateSeo('PROD001', [
'title' => 'iPhone 15 - Apple智能手机',
'description' => '全新iPhone 15,搭载A17芯片',
'keywords' => ['iPhone', 'Apple', '智能手机']
]);
echo "商品详情:\n";
$product = $productManager->getProduct('PROD001');
echo " 基本信息:\n";
echo " 名称: " . $product['basic']['name'] . "\n";
echo " 价格: " . $product['basic']['price'] . "元\n";
echo " 规格参数:\n";
foreach ($product['specs'] as $key => $value) {
echo " $key: $value\n";
}
echo " SEO信息:\n";
echo " 标题: " . $product['seo']['title'] . "\n";
echo " 关键词: " . implode(', ', $product['seo']['keywords']) . "\n";运行结果:
商品详情:
基本信息:
名称: iPhone 15
价格: 6999元
规格参数:
color: 黑色
storage: 256GB
screen: 6.1英寸
SEO信息:
标题: iPhone 15 - Apple智能手机
关键词: iPhone, Apple, 智能手机9.3 挑战练习:多语言内容管理
解题思路: 设计一个多语言内容管理系统,支持文章的多语言版本存储和查询。
常见误区:
- 为每种语言创建单独字段,扩展性差
- 没有考虑语言回退机制
- 更新时覆盖整个语言对象
分步提示:
- 设计多语言内容的数据结构
- 实现内容创建功能
- 实现特定语言内容的更新功能
- 实现语言回退查询功能
- 实现批量语言更新功能
参考代码:
php
<?php
require_once 'vendor/autoload.php';
use MongoDB\Client;
class MultilingualContentManager {
private $collection;
public function __construct() {
$client = new Client("mongodb://localhost:27017");
$this->collection = $client->practice->multilingual_content;
$this->collection->createIndex(['content_id' => 1], ['unique' => true]);
}
public function createContent($contentId, $defaultLanguage = 'zh') {
$content = [
'content_id' => $contentId,
'default_language' => $defaultLanguage,
'translations' => [],
'metadata' => [
'created_at' => new MongoDB\BSON\UTCDateTime(),
'updated_at' => new MongoDB\BSON\UTCDateTime()
]
];
$result = $this->collection->insertOne($content);
return $result->getInsertedId();
}
public function setTranslation($contentId, $language, $title, $content) {
$result = $this->collection->updateOne(
['content_id' => $contentId],
[
'$set' => [
"translations.$language" => [
'title' => $title,
'content' => $content,
'updated_at' => new MongoDB\BSON\UTCDateTime()
],
'metadata.updated_at' => new MongoDB\BSON\UTCDateTime()
]
],
['upsert' => true]
);
return $result->getModifiedCount() > 0 || $result->getUpsertedCount() > 0;
}
public function getTranslation($contentId, $language = null) {
$content = $this->collection->findOne(['content_id' => $contentId]);
if (!$content) {
return null;
}
if ($language && isset($content['translations'][$language])) {
return $content['translations'][$language];
}
$defaultLang = $content['default_language'];
if (isset($content['translations'][$defaultLang])) {
return $content['translations'][$defaultLang];
}
$availableLanguages = array_keys($content['translations']);
if (!empty($availableLanguages)) {
return $content['translations'][$availableLanguages[0]];
}
return null;
}
public function getAvailableLanguages($contentId) {
$content = $this->collection->findOne(['content_id' => $contentId]);
return $content ? array_keys($content['translations']) : [];
}
public function removeTranslation($contentId, $language) {
$result = $this->collection->updateOne(
['content_id' => $contentId],
['$unset' => ["translations.$language" => '']]
);
return $result->getModifiedCount() > 0;
}
}
$i18nManager = new MultilingualContentManager();
$i18nManager->createContent('ART001', 'zh');
$i18nManager->setTranslation('ART001', 'zh', 'MongoDB入门教程', 'MongoDB是一个NoSQL数据库...');
$i18nManager->setTranslation('ART001', 'en', 'MongoDB Tutorial', 'MongoDB is a NoSQL database...');
$i18nManager->setTranslation('ART001', 'ja', 'MongoDBチュートリアル', 'MongoDBはNoSQLデータベースです...');
echo "中文版本:\n";
$translation = $i18nManager->getTranslation('ART001', 'zh');
echo " 标题: " . $translation['title'] . "\n";
echo "\n英文版本:\n";
$translation = $i18nManager->getTranslation('ART001', 'en');
echo " 标题: " . $translation['title'] . "\n";
echo "\n可用语言:\n";
$languages = $i18nManager->getAvailableLanguages('ART001');
foreach ($languages as $lang) {
echo " - $lang\n";
}
$translation = $i18nManager->getTranslation('ART001', 'fr');
echo "\n法语版本(回退到默认语言):\n";
if ($translation) {
echo " 标题: " . $translation['title'] . "\n";
}运行结果:
中文版本:
标题: MongoDB入门教程
英文版本:
标题: MongoDB Tutorial
可用语言:
- zh
- en
- ja
法语版本(回退到默认语言):
标题: MongoDB入门教程10. 知识点总结
10.1 核心要点
对象的基本操作
- 插入:使用花括号
{}定义对象,支持多层嵌套 - 查询:使用点表示法
父字段.子字段查询嵌套字段 - 更新:使用点表示法和
$set更新特定字段 - 删除:使用
$unset删除嵌套字段
- 插入:使用花括号
对象查询语义
- 字段匹配:查询对象中特定字段的值
- 对象匹配:查询整个对象是否完全匹配(字段顺序敏感)
- 存在性检查:使用
$exists检查字段是否存在
对象更新操作
- 字段级更新:使用点表示法更新特定字段,不影响其他字段
- 对象级更新:更新整个对象会替换所有字段
- 动态添加:可以动态添加新的嵌套字段
对象索引
- 嵌套字段索引:使用点表示法为嵌套字段创建索引
- 整个对象索引:可以为整个对象创建索引(不推荐)
- 复合索引:可以组合多个嵌套字段创建复合索引
对象设计原则
- 嵌套深度:建议不超过3-4层
- 对象大小:控制嵌入式对象大小,避免文档过大
- 命名规范:使用snake_case,保持命名一致性
10.2 易错点回顾
对象字段顺序问题
- 对象完全匹配要求字段顺序一致
- 使用点表示法查询避免顺序问题
对象整体覆盖问题
- 更新整个对象会替换所有字段
- 使用点表示法更新特定字段
嵌套字段不存在问题
- MongoDB会自动创建中间对象
- 更新前检查中间对象是否存在
嵌套过深问题
- 限制嵌套深度在3-4层以内
- 考虑扁平化或使用引用
对象大小限制问题
- 控制嵌入式对象大小
- 大对象考虑使用引用或GridFS
11. 拓展参考资料
11.1 官方文档链接
MongoDB官方文档 - 嵌入式文档
- 链接:https://www.mongodb.com/docs/manual/core/document/#embedded-documents
- 说明:详细介绍嵌入式文档的结构和使用方法
MongoDB官方文档 - 点表示法
- 链接:https://www.mongodb.com/docs/manual/core/document/#dot-notation
- 说明:详细介绍点表示法的语法和使用场景
MongoDB官方文档 - 嵌入式文档索引
- 链接:https://www.mongodb.com/docs/manual/core/index-compound/#compound-indexes
- 说明:详细介绍如何为嵌入式文档字段创建索引
MongoDB官方文档 - 数据建模
- 链接:https://www.mongodb.com/docs/manual/core/data-modeling-introduction/
- 说明:详细介绍MongoDB数据建模的最佳实践
PHP MongoDB驱动官方文档
- 链接:https://www.php.net/manual/en/set.mongodb.php
- 说明:PHP MongoDB驱动的官方文档,包含详细的API说明
11.2 进阶学习路径建议
基础阶段
- 掌握对象的基本CRUD操作
- 理解点表示法的使用
- 熟悉嵌套字段的查询和更新
进阶阶段
- 学习嵌套字段索引的创建和优化
- 掌握对象设计原则
- 理解嵌入与引用的选择
高级阶段
- 学习复杂嵌套结构的性能优化
- 掌握多语言、多租户等高级应用
- 理解对象在分布式环境下的行为
实战阶段
- 参与实际项目的数据建模
- 解决生产环境中的嵌套结构问题
- 设计复杂的企业级应用数据结构
后续推荐学习:
- 《MongoDB数据建模最佳实践》
- 《MongoDB聚合管道详解》
- 《MongoDB索引优化实战》
- 《MongoDB性能调优指南》
