You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Laravel:getAttribute未附加到模型与访问器重复查询问题咨询

为什么Laravel访问器每次调用都会重复执行数据库查询?

这个问题我之前也踩过坑,核心原因是默认的访问器是惰性执行的——每次你调用Auth::user()->articles_count时,都会重新执行访问器里的查询代码,完全不会自动缓存计算结果,所以调用3次就触发3次DB查询,确实挺坑的对吧?

问题根源

你写的访问器大概是类似这样的吧?

// User.php
public function getArticlesCountAttribute()
{
    return $this->articles()->count();
}

这种写法里,$this->articles()->count()每次被调用时,都会生成一条新的SELECT COUNT(*) FROM articles WHERE user_id = ?查询。Laravel不会帮你自动缓存这个值,因为访问器的设计逻辑就是“按需计算”,而非“初始化时预计算”。

解决办法(按推荐优先级排序)

1. 用Laravel原生的withCount预加载(最推荐)

这是Laravel官方为关联计数场景提供的最优方案,既能避免重复查询,性能表现也更好。

  • 手动预加载:在获取用户的时候,提前把文章计数加载好

    // 比如在控制器里获取当前用户时
    $user = Auth::user()->withCount('articles')->first();
    

    之后在视图里不管调用多少次$user->articles_count,都是用的预加载好的值,不会再触发数据库查询。

  • 全局自动预加载:如果项目里每个地方都需要这个计数,可以给User模型加全局作用域,自动带上这个统计:

    // User.php
    protected static function booted()
    {
        static::addGlobalScope('articlesCount', function ($query) {
            $query->withCount('articles');
        });
    }
    

    这样每次获取User模型实例时,都会自动执行一次带计数的查询,之后所有调用都用缓存好的结果。

2. 在访问器内手动缓存结果

如果你不想用withCount,可以自己在访问器里把计算结果存在模型的attributes数组里,避免重复计算:

// User.php
public function getArticlesCountAttribute()
{
    // 先检查有没有已经计算好的值
    if (!isset($this->attributes['articles_count'])) {
        // 计算后存在attributes里
        $this->attributes['articles_count'] = $this->articles()->count();
    }
    return $this->attributes['articles_count'];
}

第一次调用时会执行查询并缓存,之后再调用就直接返回缓存的值,不会再触发DB查询。

3. 使用缓存系统(适合非实时场景)

如果文章数量不需要实时更新,可以用Laravel的缓存功能把结果存起来,比如缓存1小时:

// User.php
use Illuminate\Support\Facades\Cache;

public function getArticlesCountAttribute()
{
    return Cache::remember("user_{$this->id}_articles_count", now()->addHour(), function () {
        return $this->articles()->count();
    });
}

这样在缓存有效期内,不管调用多少次,都是用缓存里的值,不会查询数据库。

内容的提问来源于stack exchange,提问作者Badal

火山引擎 最新活动