close

N+1 Problem

今天看document看到N+1 problem,基於laravel是lazy loading所以有可能因此寫出效率很差的sql指令,之前在玩laravel的時候就使用

/laravel-debugbar

透過這個工具發現,奇怪我的sql怎麼在我使用foeach的時候居然一筆一筆去作query

$books = App\Book::all();

foreach ($books as $book) {
    echo $book->author->name;
}

  就像這個範例一樣,假設我從book select * from book; 拿到 25本書

然後我再foreach的時候要從每一本書中拿出author的name,所以就會有所謂的n+1 problem

因為是lazy loading所以在foreach時,laravel會一筆一筆去作query   ---->  select name from author where book.id = author.id 

因為連續25筆就會連續select 25次,這個假如是使用者多一點mysql不掛給你看才奇怪,

根本就是自己寫出來的DDOS了XDD

我原本還以為Eloquent怎麼會做成這樣子,還以為ORM居然只有這點程度。

直到翻到laravel Documentation才知道可以轉換成Eager loading

$books = App\Book::with('author')->get();

foreach ($books as $book) {
    echo $book->author->name;
}

  透過With告知Eloquent我之後會使用到author這個model。

select * from books

select * from authors where id in (1, 2, 3, 4, 5, ...)

他就會縮減成兩個sql而已,因此增加極大的效能,小地方不得不注意,!!!!!!

$books = App\Book::with(['author', 'publisher'])->get();

  針對多重關係的eager loading

$books = App\Book::with('author.contacts')->get();

  與上述相 同,使用 dot來比較好書寫。少打好幾個   '  果然有artisan的感覺!!!

$users = App\Book::with('author:id,name')->get();

  只select出特定的內容 ,在這裡只select出author的name,  !!!!另外特別注意 要單純只挑幾個name出來必須要包含author:id 在開頭    !!!!!   

$users = App\User::with(['posts' => function ($query) {
    $query->where('title', 'like', '%first%');
}])->get();

 

增加限制條件!!

The limit and take query builder methods may not be used when constraining eager loads.

當有限制條件limit和take就沒辦法一起使用囉!! 

 

$books = App\Book::all();

if ($someCondition) {
    $books->load('author', 'publisher');
}

  同樣是eager loading可是你可以選擇晚一點再Loading近來

有點像是你先select * from book;

之後在等$someCondition = true時,select * from author, publisher where book.id = .......

一樣是只有執行兩次!!!! 

$books->load(['author' => function ($query) {
    $query->orderBy('published_date', 'asc');
}]);

在lazy eager loading使用constraints.

public function format(Book $book)
{
    $book->loadMissing('author');

    return [
        'name' => $book->name,
        'author' => $book->author->name
    ];
}

loadMissing,先檢查有沒有load過,沒有才加載

$comment = new App\Comment(['message' => 'A new comment.']);

$post = App\Post::find(1);

$post->comments()->save($comment);

  這個使用過php artisan tinker應該就知道概念了!!!

這比較特別一點是我把新增的comment丟到特定的post裡面,非常的有OOP。

$post = App\Post::find(1);

$post->comments()->saveMany([
    new App\Comment(['message' => 'A new comment.']),
    new App\Comment(['message' => 'Another comment.']),
]);

  同時save多個comment 0.0

$post = App\Post::find(1);

$post->comments[0]->message = 'Message';
$post->comments[0]->author->name = 'Author Name';

$post->push();

 

push 可以save多個改過的資料。 狂!!

In addition to the save and saveMany methods, you may also use the create method, which accepts an array of attributes, creates a model, and inserts it into the database. Again, the difference between save and create is that save accepts a full Eloquent model instance while create accepts a plain PHP array:

$post = App\Post::find(1);

$comment = $post->comments()->create([
    'message' => 'A new comment.',
]);

  create只接受plain PHP array 可是save可以把Object直接存進去。

且create method不允許mass assignment,什麼是mass assignment,根據SO的說法

https://stackoverflow.com/questions/22279435/what-does-mass-assignment-mean-in-laravel

$user = new User(Input::all());

 ​​​​​​我們可以在laravel中直接使用array執行create,但是這樣有機率性被強制塞入一個field叫做 user_type = admin

而造成安全性問題,所以我們必須在Model中使用fillable array來限制說只有那些屬性是可以透過此方法 or 陣列來create User,​

然而$guarded = ['price'] 則代表不接受 mass assignment,與fillable剛好相反。

而沒有被fillable的屬性只能使用$user->user_type = 'admin'

$user->save()來執行了,避免透過 user傳過來的惡意屬性攻擊。

 

 

arrow
arrow
    文章標籤
    laraval sql n+1
    全站熱搜

    蕭瑞文 發表在 痞客邦 留言(0) 人氣()