【问题标题】:Laravel 4 Eager Loading n+1 issueLaravel 4 Eager Loading n+1 问题
【发布时间】:2014-05-28 19:27:41
【问题描述】:

我整天都在解决急切加载/n+1 问题,研究、阅读和观看了有关此问题的教程,但尚未解决。我已经为模型设置了关系,但是当我使用辅助函数传入数据时,我遇到了这个 n+1 问题。 我想从 url site.com/Artist/songs 中获取一个艺术家的名字并获取它的所有歌曲并显示一个这样的 url。

site.com/$Artist/songs/$id

我的艺术家/index.blade.php 视图看起来像这样http://i61.tinypic.com/2nqzatk.jpg 我不确定我在这里缺少什么。

提前致谢!

我的桌子

歌曲 id、标题、正文、slug、点击数、artist_id、created_at、updated_at

艺术家 id, name, body, created_at, updated_at

routes.php

Event::listen('illuminate.query', function($query)
{
    var_dump($query);
});
...
Route::get('{artist}/songs', 'ArtistsController@index');
Route::get('{artist}/songs/{id}', ['as' => 'artist.songs.show', 'uses' => 'ArtistsController@show']);

型号:Song.php

class Song extends Eloquent {
    protected $guarded = ['id'];

    /**
     * Setting up relationship for the artist model for easier usage
     *
     */
    public function artist()
    {
        return $this->belongsTo('Artist');
    }

    // Override find method
    public static function find($id, $name = null)
    {
        $song = static::with('artist')->find($id);

        // If the song is found
        if ($song)
        {
            // If the song doesn't belong to that artist, throw an exception which will redirect to home, defined in global.php
            if ($name and $song->artist->name !== $name)
            {
                throw new Illuminate\Database\Eloquent\ModelNotFoundException;
            }

            return $song;
        }
        // If the song is not found, throw an exception which will redirect to home, defined in global.php
        else
        {
            throw new Illuminate\Database\Eloquent\ModelNotFoundException;
        }

    }

    // Get songs from artist
    public static function byArtist($name)
    {
        return Artist::byName($name)->songs;
    }

}

模型艺术家.php

class Artist extends Eloquent {
    protected $fillable = [];

    /**
     * Setting up relationship with the song model for easier usage
     * $artist->songs;
     */
    public function songs()
    {
        return $this->hasMany('Song');
    }

    // Get artist by name
    public static function byName($name)
    {
        return static::whereName($name)->first();
    }

}

控制器:ArtistsController.php

class ArtistsController extends BaseController {

    // Set default layout for this controller
    protected $layout = 'layouts.master';

    /**
     * Display a listing of the resource.
     * GET /artists
     *
     * @return Response
     */
    public function index($name)
    {
        $this->data['songs'] = Song::byArtist($name);

        $this->layout->content = View::make('artists.index', $this->data);
    }

helpers.php

function link_to_artist_song(Song $song)
{
    return link_to_route('artist.songs.show', $song->title, [$song->artist->name, $song->id]);
}

艺术家的索引视图 艺术家/index.blade.php http://i61.tinypic.com/2nqzatk.jpg

@extends('layouts.master')

@section('content')

    @if(isset($songs))
        <h1>All Songs</h1>

        <ul class="list-group">
        @foreach($songs as $song)
            <li class="list-group-item">{{ link_to_artist_song($song) }}</li>
        @endforeach
        </ul>
    @endif
@stop

【问题讨论】:

    标签: php laravel eager-loading


    【解决方案1】:

    当您有很多歌曲和很多艺术家时,就会出现 n+1 问题。它必须获取所有歌曲(1 个查询),然后为每首歌曲获取艺术家(n 个查询)。

    在这种情况下,您已经知道这首歌,所以这是一个查询,然后您需要该歌曲的所有艺术家,这只是一个额外的查询。

    n+1 问题只有在您尝试查找某一类型的歌曲时才会发挥作用,例如,这可能会返回许多歌曲。然后,对于每首歌曲,您必须进行额外的查询以获取该歌曲的艺术家。这将是急切加载最有用的地方。

    $song = Song::find($id);
    $artist = $song->artist;
    

    【讨论】:

    • 你的答案第一部分是真的,第二部分不是很清楚。
    • 我稍微修改了一下,希望更清晰。
    【解决方案2】:

    您从不急于加载任何内容,这就是您可能面临 n+1 问题的原因。 如果我猜对了,你在这里的代码有什么难的地方,你想要给定艺术家的所有歌曲,其中 $name 来自 url,对吧?

    所以这里有你需要的一切:

    // controller
    public function index($name)
    {
        // with('songs') is eager loading related songs for you
        $this->data['artist'] = Artist::with('songs')->whereName($name)->first();
    
        $this->layout->content = View::make('artists.index', $this->data);
    }
    
    // the problem of your queries is in the helper:
    function link_to_artist_song(Song $song)
    {
        return link_to_route('artist.songs.show', $song->title, [
           $song->artist->name, // this is calling db query for each song to retrieve its artist (despite it is always the same)
           $song->id]);
    }
    
    // so instead use this in your view
    @foreach($artist->songs as $song)
       <li class="list-group-item">
         {{ link_to_route('artist.songs.show', $song->title, [$artist->name, $song->id]) }}
       </li>
    @endforeach
    

    【讨论】:

    • 是的,没错,我想从 url 获取具有 $name 的给定艺术家的所有歌曲。我用你的代码行替换了$this-&gt;data['songs'] = Artist::with('songs')-&gt;whereName($name)-&gt;first(); 现在我的助手遇到了这个错误。 “传递给 link_to_artist_song() 的参数 1 必须是 Song 的一个实例,给定布尔值,调用...”所以这些方法根本不适用于获取歌曲?歌曲模特public static function byArtist($name) { return Artist::byName($name)-&gt;songs; }艺人模特public static function byName($name) { return static::whereName($name)-&gt;first(); }
    • 我希望能够获取艺术家姓名并显示这样的链接 domain.com/ArtistName/songs/1 这就是我想要急切加载的原因。
    • 您想列出一个给定艺术家的所有歌曲 name = $name,所以您只需要一位艺术家和所有相关的歌曲。除非你想要别的东西。几秒钟后检查我的编辑
    【解决方案3】:

    每次你抓取一首歌,然后做$song-&gt;artist,它都会做一个查询。

    您也可以使用Query scopes。

    class Song extends Eloquent {
    
        public function scopeByArtist($query, $name) {
            return Artist::whereName($name)->first()->songs(); //->with('artist');
        }
    }
    

    像这样,艺术家已经加载完毕。

    查询:

    $songs = Song::byArtist($name)->get();
    

    【讨论】:

    • 你忘记了 scope 方法中的第一个参数,它不会加载艺术家
    猜你喜欢
    • 1970-01-01
    • 2019-01-08
    • 1970-01-01
    • 1970-01-01
    • 2021-10-17
    • 1970-01-01
    • 2011-09-29
    • 1970-01-01
    • 2017-08-19
    相关资源
    最近更新 更多