权限校验变慢主因是重复查库,可通过预加载roles、缓存角色数据、多级关联预加载及Redis缓存四步优化,将10次SQL降至1次并复用结果。

权限校验变慢,往往不是逻辑本身复杂,而是每次调用都重复查数据库。比如 auth()->user()->isCustomer() 在一个请求里被调用了十几次,每次都在查 roles 表,结果就是 10 次 SQL —— 这类问题用 Eager Loading 预加载就能从根源上解决。
把角色数据一次性加载进内存
用户模型中定义了 belongsToMany(Role::class) 关系,但默认是懒加载。只要在认证后首次获取用户时就预加载角色,后续所有角色判断都不再碰数据库。
- 登录成功后,在中间件或控制器里这样取用户:User::with('roles')->findOrFail($id)
- 或者用
auth()->user()->load('roles')延迟加载一次(适合已登录但没预加载的场景) - 确保
auth()->user()返回的是已加载 roles 的实例,而不是原始未加载对象
重写 hasRole 方法,避免重复查询
原来每次调用 hasRole() 都执行 $this->roles()->where(...)->first(),现在可以直接查内存里的集合。
- 把角色缓存在属性里:$this->cachedRoles ??= $this->getRelationValue('roles')?->pluck('name')->toArray() ?: []
- 然后
hasRole($role)只需 in_array($role, $this->cachedRoles) - 如果角色有启用状态等条件,预加载时就带上过滤:with(['roles' => fn ($q) => $q->where('enabled', 1)])
多层级权限关联也适用预加载
当权限结构是「用户 → 角色 → 权限」时,点号语法能一层带到底,避免嵌套查询爆炸。
- 加载用户及其全部可用权限:User::with('roles.permissions')->get()
- 只加载启用的权限:with(['roles.permissions' => fn ($q) => $q->where('enabled', 1)])
- 注意控制层级深度,roles.permissions.actions 这种三层以上嵌套容易引发笛卡尔积,优先考虑是否真需要全量加载
配合缓存进一步降压
预加载解决了单次请求内的重复查询,但如果权限变更不频繁,还可以加一层缓存让整块数据复用。
- 用 Cache::remember("user:{$userId}:roles", 3600, fn () => User::with('roles')->find($userId)?->roles)
- 权限更新时主动清除缓存:Cache::forget("user:{$userId}:roles")
- 对超级高频接口,甚至可缓存整个权限判断结果,如
isCustomer的布尔值

















