laravel-mongodb模型关系详解:嵌入式文档与引用关系对比
【免费下载链接】laravel-mongodb 项目地址: https://gitcode.com/gh_mirrors/lar/laravel-mongodb
在使用MongoDB数据库时,开发者常常需要在嵌入式文档(Embedded Documents)和引用关系(Referenced Relationships)之间做出选择。这两种关系模型各有适用场景,选择不当可能导致性能瓶颈或数据一致性问题。本文将深入对比laravel-mongodb中的嵌入式文档(embedsOne/embedsMany)与引用关系(hasOne/hasMany),帮助开发者根据业务场景做出最优决策。
关系模型核心差异
嵌入式文档和引用关系的本质区别在于数据存储方式:嵌入式文档将关联数据直接存储在父文档内部,而引用关系通过ID指针关联不同集合的文档。
嵌入式文档
嵌入式文档通过embedsOne(一对一)和embedsMany(一对多)方法定义,数据存储在父文档的字段中。例如,宇宙飞船(SpaceShip)可以嵌入多个货物(Cargo)文档:
// [docs/includes/eloquent-models/relationships/embeds/SpaceShip.php](https://link.gitcode.com/i/258fa40904de843a9e6be72381b72700)
namespace App\Models;
use MongoDB\Laravel\Eloquent\Model;
use MongoDB\Laravel\Relations\EmbedsMany;
class SpaceShip extends Model
{
protected $connection = 'mongodb';
public function cargo(): EmbedsMany
{
return $this->embedsMany(Cargo::class);
}
}
存储结构示例:
{
"_id": "ObjectId(...)",
"name": "Millennium Falcon",
"cargo": [
{"name": "spice", "weight": 50, "_id": "ObjectId(...)"},
{"name": "hyperdrive", "weight": 25, "_id": "ObjectId(...)"},
]
}
引用关系
引用关系通过hasOne/hasMany(一对多)和belongsTo(反向关联)定义,关联数据存储在独立集合中,通过外键(如planet_id)关联。例如,行星(Planet)拥有多个卫星(Moon):
// [docs/includes/eloquent-models/relationships/one-to-many/Planet.php](https://link.gitcode.com/i/e3b1fb133eecfd811e12f219c4fa70c3)
namespace App\Models;
use MongoDB\Laravel\Eloquent\Model;
use MongoDB\Laravel\Relations\HasMany;
class Planet extends Model
{
protected $connection = 'mongodb';
public function moons(): HasMany
{
return $this->hasMany(Moon::class);
}
}
卫星集合中的文档通过planet_id关联行星:
// moons集合
{
"_id": "ObjectId(...)",
"name": "Europa",
"planet_id": "ObjectId(...)" // 引用行星ID
}
性能对比与适用场景
读取性能
嵌入式文档读取性能显著优于引用关系,因为一次数据库查询即可获取所有关联数据。例如,获取宇宙飞船及其货物时,嵌入式模型只需查询space_ships集合,而引用关系需要分别查询planets和moons集合(产生N+1查询问题)。
适用场景:关联数据需频繁一起读取的场景(如订单-订单项)。
写入性能
- 嵌入式文档:写入时会锁定整个父文档,适用于小数据量更新。大数据量嵌入可能导致文档过大(MongoDB建议单文档不超过16MB)。
- 引用关系:子文档可独立写入,适合高并发更新场景(如社交媒体评论)。
数据一致性
嵌入式文档天然支持原子更新,可通过单次操作更新父文档及所有嵌入数据:
// 原子更新嵌入式文档
$spaceship->cargo()->update(['weight' => 60]);
引用关系需手动维护一致性,可通过事务(Transactions)确保多文档更新的原子性:
// [docs/includes/fundamentals/transactions/TransactionsTest.php](https://link.gitcode.com/i/fd2cc80b8375f6bb58e27db4341b0b6e)
DB::transaction(function () {
$planet->update(['name' => 'Jupiter']);
$planet->moons()->update(['orbital_period' => 173]);
});
功能支持对比
| 功能特性 | 嵌入式文档 | 引用关系 |
|---|---|---|
| 跨集合关联 | ❌ 不支持 | ✅ 支持(可跨数据库) |
| 延迟加载(Lazy Loading) | ❌ 已随父文档加载 | ✅ 支持,需额外查询 |
| 分页查询 | ✅ 支持(内存分页) | ✅ 支持(数据库级分页) |
| 索引支持 | ✅ 支持嵌入字段索引 | ✅ 支持外键索引 |
| 数据独立访问 | ❌ 需通过父文档访问 | ✅ 可直接查询子文档集合 |
决策指南与最佳实践
优先选择嵌入式文档当:
- 关联数据与父文档生命周期一致(如文章-评论)。
- 需频繁一起读取,且数据量较小(如用户-个人资料)。
- 需原子更新关联数据(如订单状态变更)。
优先选择引用关系当:
- 关联数据需独立访问或跨集合查询(如用户-订单)。
- 数据量较大或更新频繁(如社交媒体动态)。
- 存在多对多关系(需通过
belongsToMany实现)。
关系定义代码示例
嵌入式文档完整示例
// 父模型:SpaceShip
namespace App\Models;
use MongoDB\Laravel\Eloquent\Model;
use MongoDB\Laravel\Relations\EmbedsMany;
class SpaceShip extends Model
{
protected $connection = 'mongodb';
public function cargo(): EmbedsMany
{
return $this->embedsMany(Cargo::class);
}
}
// 嵌入模型:Cargo
class Cargo extends Model
{
protected $fillable = ['name', 'weight'];
}
// 使用示例
$ship = SpaceShip::create(['name' => 'Millennium Falcon']);
$ship->cargo()->create(['name' => 'spice', 'weight' => 50]);
引用关系完整示例
// 父模型:Planet
namespace App\Models;
use MongoDB\Laravel\Eloquent\Model;
use MongoDB\Laravel\Relations\HasMany;
class Planet extends Model
{
protected $connection = 'mongodb';
public function moons(): HasMany
{
return $this->hasMany(Moon::class);
}
}
// 子模型:Moon
class Moon extends Model
{
protected $fillable = ['name', 'planet_id'];
}
// 使用示例
$planet = Planet::create(['name' => 'Jupiter']);
$planet->moons()->create(['name' => 'Ganymede']);
官方文档与进阶资源
- 关系模型官方文档:docs/eloquent-models/relationships.txt
- 事务支持:docs/includes/fundamentals/transactions/TransactionsTest.php
- 性能优化指南:docs/performance.txt
通过合理选择关系模型,可充分发挥MongoDB的文档数据库优势。嵌入式文档适合数据聚合场景,引用关系适合灵活扩展场景,实际开发中也可结合两者(如混合使用嵌入和引用)以满足复杂业务需求。
【免费下载链接】laravel-mongodb 项目地址: https://gitcode.com/gh_mirrors/lar/laravel-mongodb
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



