- vừa được xem lúc

[Laravel] Giải quyết vấn đề bộ nhớ khi sử dụng Eager Loading

0 0 73

Người đăng: Phát Nhâm

Theo Viblo Asia

Hình ảnh minh hoạ

Vấn đề

Hôm nay chúng ta sẽ đưa đến một vấn đề không phải mới, và chắc là các bạn cũng đã từng giải quyết rồi. Lấy một đối tượng từ quan hệ one-many, ví dụ ta có 2 đối tượng là PostComment như sau đây:

Giả sử một ngày đẹp trời, bạn sẽ cần lấy ra một danh sách bài viết và các bình luận mới nhất từng bài viết đó, bạn sẽ làm thế nào? Chúng ta có rất nhiều cách để thực hiện đề bài nàu, hãy cùng nhau tìm hiểu qua từng cách làm cũng như ưu và khuyết điểm của mỗi cách tiếp cận nhé

Sử dụng relationship

Trong Laravel bạn dễ dàng thực hiện được việc này thông qua model Post và quan hệ comments, mình bỏ qua bước tạo project nhé, ta đi tiếp vào ví dụ dưới đây:

// Trong class Post.php ta có:
public function comments()
{ return $this->hasMany('App\Models\Comment');
}

Và trong controller ta chỉ cần gọi all() và truyền dữ liệu qua view:

$posts = Post::all();
return view('list', compact('posts'));

Trong view sẽ có 2 cột, tên bài viếtbình luận mới nhất:

<table> <thead> <tr> <th>Bài viết</th> <th>Bình luận</th> </tr> </thead> <tbody> @foreach($posts as $post) <tr> <td>{{ $post->title }}</td> <td>{{ $post->comments->sortByDesc('created_at')->first()->content }}</td> </tr> @endforeach </tbody>
</table>

Kết quả:

12 lần truy vấn CSDL trong một lần, đối với ví dụ này, số lượng không phải là quá nhiều, tuy nhiên với một số lượng lớp DB đến vài ngàn bài viết thì có vẻ sẽ rất tệ.

Sử dụng relationship và Eager loading

Như đã nói ở bài viết trước, vấn đề truy vấn của Laravel có thể dễ dàng giải quyết bằng Eager loading. Ta sẽ tạo các relationship trong các model Post như sau:

// HasMany
public function comments()
{ return $this->hasMany('App\Models\Comment');
}

Controller:

$posts = Post::with('comments')->get();

View:

<table> <thead> <tr> <th>Bài viết</th> <th>Bình luận</th> </tr> </thead> <tbody> @foreach($posts as $post) <tr> <td>{{ $post->title }}</td> <td>{{ $post->comments->sortByDesc('created_at')->first()->content }}</td> </tr> @endforeach </tbody>
</table>

Kết quả:

Vẫn là Eager Loading, nhưng là hasOne

Như bạn thấy, chúng ta đã giảm lượng truy vấn xuống còn 2 truy vấn, bạn cũng có thể tối ưu cho code đẹp hơn bằng cách tạo quan hệ hasOne giữa 2 đối tượng.

// HasOne
public function latest_comment()
{ return $this->hasOne('App\Models\Comment')->latest();
}

Controller:

$posts = Post::with('latest_comment')->get();

View:

<table> <thead> <tr> <th>Bài viết</th> <th>Bình luận</th> </tr> </thead> <tbody> @foreach($posts as $post) <tr> <td>{{ $post->title }}</td> <td>{{ $post->latest_comment->content }}</td> </tr> @endforeach </tbody>
</table>

Kết quả:

Hola! code đã đẹp và rất dễ đọc. Tuy nhiên (lại tuy nhiên) nếu bạn để ý thấy ta chỉ cần dùng 20 model (10 post và 10 comment mới nhất) nhưng ở đây lại load đến 10010 model, tức là nó sẽ lấy ra 10 bài viết và tất cả bình luận của 10 bài viết đó ?? Nếu bạn có một máy chủ không giới hạn dung lượng, việc này không sao, tuy nhiên nó sẽ làm giảm đáng kể khả năng xử lý và có vẻ không ổn, Hãy luôn ghi nhớ:

Database queries first, memory usage second

Giải quyết bằng Dynamic relationship

Trong ví dụ trên, ta đã thành công trong việc giảm thiểu tối đa các truy vấn không cần thiết nhưng vô tình đã làm tăng dung lượng ram. Hãy luôn nhớ "Database queries first, memory usage second" Việc này có thể giải quyết bằng cách thực hiện một Subquery Select và tạo một relationship belongsTo cho Post

Nhìn vào hình ở trên cho dễ hiểu, khi thực hiện truy vấn, ta sẽ thêm vào một cột tên là latest_comment_id, cột này được lấy từ bảng comments với các điều kiện đặt trước.

// Relationship
public function latest_comment()
{ return $this->belongsTo('App\Models\Comment', 'latest_comment_id', 'id');
}
// Subquery
public function scopeWithLatestComment($query)
{ $query->addSelect([ 'latest_comment_id' => Comment::select('id') ->whereColumn('post_id', 'posts.id') ->orderBy('created_at', 'desc') ->take(1) ])->with('latest_comment');
}

Controller:

$posts = Post::withLatestComment()->get();

View:

<table> <thead> <tr> <th>Bài viết</th> <th>Bình luận</th> </tr> </thead> <tbody> @foreach($posts as $post) <tr> <td>{{ $post->title }}</td> <td>{{ $post->latest_comment->content }}</td> </tr> @endforeach </tbody>
</table>

Kết quả:

Bingo! kết quả chỉ có 2 truy vấn, và 20 model được tải lên ứng dụng, bộ nhớ sử dụng đã giảm từ 33mb ~ 18mb. Vậy là vừa đảm bảo được 2 tiêu chí đặt ra.

Tham khảo:

Bình luận

Bài viết tương tự

- vừa được xem lúc

Tìm hiểu về Resource Controller trong Laravel

Giới thiệu. Trong laravel, việc sử dụng các route post, get, group để gọi đến 1 action của Controller đã là quá quen đối với các bạn sử dụng framework này.

0 0 335

- vừa được xem lúc

Phân quyền đơn giản với package Laravel permission

Như các bạn đã biết, phân quyền trong một ứng dụng là một phần không thể thiếu trong việc phát triển phần mềm, dù đó là ứng dụng web hay là mobile. Vậy nên, hôm nay mình sẽ giới thiệu một package có thể giúp các bạn phân quyền nhanh và đơn giản trong một website được viết bằng PHP với framework là L

0 0 421

- vừa được xem lúc

Sử dụng Swagger để xây dựng API documentation

Giới thiệu về Swagger. RESTful API là một tiêu chuẩn dùng trong việc thiết kế API cho các ứng dụng web (thiết kế Web services) để tiện cho việc quản lý các resource.

0 0 1k

- vừa được xem lúc

Ví dụ CRUD với Laravel và Vuejs.

1. Cài đặt Laravel. composer create-project --prefer-dist laravel/laravel vuelaravelcrud. .

0 0 141

- vừa được xem lúc

Một số tips khi dùng laravel (Part 1)

1. Show database query in raw SQL format. DB::enableQueryLog(); // Bật tính năng query logging. DB::table('users')->get(); // Chạy truy vấn bạn muốn ghi log.

0 0 69

- vừa được xem lúc

Inertiajs - Xây dựng Single Page App không cần API

Tiêu đề là mình lấy từ trang chủ của https://inertiajs.com/ chứ không phải mình tự nghĩ ra đâu nhé :v. Lâu lâu rồi chưa động tới Laravel (dự án cuối cùng mình code là ở ver 5.8), thế nên một ngày đẹp trời lượn vào đọc docs ver 8.

0 0 227