在 Laravel 项目里,用户和角色的多对多关联是权限管理的常见设计,很多开发者在获取用户角色时,会因为代码逻辑的不合理导致重复的数据库查询,比如在一个请求中多次调用获取角色的方法,或者遍历用户列表时逐个查询角色。这类问题会直接增加数据库连接开销,降低接口响应速度。

常见的重复查询场景
最常见的重复查询出现在用户列表遍历的场景中,比如下面的代码:
<?php
// 用户模型 User.php 中定义角色关联
public function roles()
{
return $this->belongsToMany(Role::class);
}
// 控制器中的查询逻辑
$users = User::all();
foreach ($users as $user) {
// 每次循环都会发起一次角色查询,N个用户就会产生N+1次查询
$roles = $user->roles;
// 其他业务逻辑
}这种写法会导致典型的 N+1 查询问题,当数据量较大时性能下降会非常明显。
优化方案一:使用预加载关联
Laravel 的预加载功能可以在查询用户时一次性把关联的角色数据加载进来,避免后续重复查询。
<?php
// 使用 with 方法预加载角色关联
$users = User::with('roles')->get();
foreach ($users as $user) {
// 此时不会再发起数据库查询,直接从预加载的数据中获取角色
$roles = $user->roles;
// 其他业务逻辑
}预加载会把所有用户的角色通过两条 SQL 完成查询,一条查用户,一条查所有用户对应的角色,大幅减少数据库请求次数。
优化方案二:使用缓存减少查询
如果用户的角色信息更新频率不高,可以把角色数据缓存起来,避免重复查询数据库。
<?php
// 获取用户角色时先查缓存
public function getUserRoles($userId)
{
$cacheKey = 'user_' . $userId . '_roles';
// 缓存1小时,根据实际情况调整时间
return Cache::remember($cacheKey, 3600, function () use ($userId) {
$user = User::find($userId);
return $user->roles;
});
}
// 调用方法获取角色
$roles = $this->getUserRoles(1);
// 再次调用相同用户的角色获取时,直接从缓存读取,不会查询数据库
$rolesAgain = $this->getUserRoles(1);当角色信息更新时,需要主动清除对应的缓存,保证数据一致性:
<?php
// 角色更新后清除缓存
public function updateUserRoles($userId, $roleIds)
{
$user = User::find($userId);
$user->roles()->sync($roleIds);
// 清除该用户的角色缓存
Cache::forget('user_' . $userId . '_roles');
}优化方案三:自定义访问器减少重复调用
可以在用户模型中定义角色相关的访问器,把角色数据缓存到模型实例中,避免同一个模型实例多次调用关联方法时重复查询。
<?php
// User.php 模型中添加访问器
private $cachedRoles = null;
public function getCachedRoles()
{
if (is_null($this->cachedRoles)) {
$this->cachedRoles = $this->roles;
}
return $this->cachedRoles;
}
// 调用方式
$user = User::find(1);
// 第一次调用会查询数据库
$roles1 = $user->getCachedRoles();
// 第二次调用直接从实例缓存获取,不再查询
$roles2 = $user->getCachedRoles();不同场景的方案选择
如果是列表遍历类的查询,优先选择预加载方案,性能提升最明显;如果是单个用户角色多次跨方法调用,缓存方案更合适;如果是同一个模型实例内多次获取角色,自定义访问器的方式更轻量。可以根据实际业务场景组合使用这些方案,达到最优的查询性能。