Laravel Eloquent预加载嵌套关系时IN子句重复ID问题
Laravel Eloquent嵌套预加载时IN子句出现大量重复ID
环境
- 操作系统:Windows 11 - WSL2(Ubuntu 20.04)
- PHP:8.1
- 技术栈:Laravel 9.22 | InertiaJS | Vue3
- 数据库:mariadb:10.7.4
- 本地Web服务器:nginx
问题描述
在Laravel项目中使用Eloquent进行嵌套关系预加载时,执行$user->load(['auth','page.articles'])或控制器中$group->load(['users.page.articles','page' => ['articles','cover']])这类语句,生成的SQL查询里IN子句包含大量重复ID(例如in (1,1,...))。
关键背景
Page模型使用多态关联morphTo(对应字段pageable_id、pageable_type)- 项目基于InertiaJS开发
- 所有模型通过自定义
HasUuidtrait使用UUID(非Laravel内置实现)
排查与解决思路
1. 修正自定义HasUuid trait的主键逻辑
自定义UUID实现如果没正确覆盖Laravel的主键核心方法,会导致Eloquent无法识别唯一ID,进而在预加载时重复收集ID。重点检查并补全以下逻辑:
- 模型中需设置
public $incrementing = false;,因为UUID不是自增类型 - 确保
trait正确实现getKeyType()返回string,getKeyName()返回你的UUID字段名 - 确认
bootHasUuid方法中生成UUID的逻辑正确
示例修正代码:
trait HasUuid { protected static function bootHasUuid() { static::creating(function ($model) { if (! $model->getKey()) { $model->{$model->getKeyName()} = Str::uuid()->toString(); } }); } public function getKeyName() { return 'id'; // 替换为你的UUID字段实际名称 } public function getKeyType() { return 'string'; } public function isIncrementing() { return false; } }
2. 校验多态关联的定义正确性
多态关联的morphTo如果关联名称或反向关联定义错误,会导致Eloquent预加载时无法正确分组模型,重复生成ID:
- 确保
Page模型的morphTo关联名称与数据库字段对应:
// Page模型 public function pageable() { return $this->morphTo(); }
- 关联的User、Group等模型需正确定义反向多态关联:
// User模型示例 public function page() { return $this->morphOne(Page::class, 'pageable'); }
3. 预加载时手动对ID去重
如果上述调整后仍有问题,可在预加载闭包中强制对ID去重,让IN子句只保留唯一值:
// 针对user的page.articles预加载 $user->load([ 'auth', 'page.articles' => function ($query) use ($user) { $uniquePageIds = collect($user->page->pluck('id'))->unique()->values()->toArray(); $query->whereIn('page_id', $uniquePageIds); } ]); // 针对group的嵌套预加载 $group->load([ 'users.page' => function ($query) { $query->distinct('id'); }, 'users.page.articles', 'page' => ['articles','cover'] ]);
4. 升级Laravel版本
Laravel 9.22存在部分预加载相关的小bug,官方后续在9.x分支修复过关联预加载的ID收集逻辑,可尝试升级到同分支的最新稳定版。
内容的提问来源于stack exchange,提问作者Gaël Duval
相关产品推荐
相关产品推荐

