
# Vấn Đề N+1 Query Trong Eloquent
Vấn đề N+1 query là một trong những vấn đề phổ biến mà các nhà phát triển gặp phải khi làm việc với ORM như Eloquent trong Laravel. Vấn đề này xảy ra khi ứng dụng của bạn thực hiện một lượng lớn các truy vấn nhỏ lặp đi lặp lại để lấy dữ liệu liên quan, thay vì chỉ thực hiện một vài truy vấn lớn hơn, dẫn đến giảm hiệu suất ứng dụng. Dưới đây là bốn ví dụ về vấn đề N+1 query và cách giải quyết chúng.
Trước khi vào các ví dụ cụ thể, bạn nên cài đặt Laravel Debugbar để có thể kiểm tra và debug các ứng dụng Laravel một cách dễ dàng. Debugbar hiển thị các thông tin chi tiết về các truy vấn cơ sở dữ liệu, các biến session, các yêu cầu HTTP và nhiều thông tin khác ngay trên giao diện của ứng dụng.
composer require barryvdh/laravel-debugbar --dev
Tiếp theo, bạn chỉ cần kích hoạt chế độ gỡ lỗi với biến APP_DEBUG=true trong file .env
// .env
APP_DEBUG=true
Ví Dụ 1: Lấy Dữ Liệu Liên Quan Trong Vòng Lặp
Mã Gặp Vấn Đề N+1 Query:
$posts = Post::all();
foreach ($posts as $post) {
echo $post->user->name;
}
Giải Thích:
Đoạn mã trên sẽ thực hiện một truy vấn để lấy tất cả các bài viết. Sau đó, nó sẽ thực hiện một truy vấn riêng lẻ cho mỗi bài viết để lấy thông tin người dùng liên quan, dẫn đến tổng cộng 1 + N truy vấn (N là số lượng bài viết).
Giải Quyết Sử Dụng Eager Loading:
$posts = Post::with('user')->get();
foreach ($posts as $post) {
echo $post->user->name;
}
Giải Thích:
Bằng cách sử dụng phương thức with
, chúng ta chỉ thực hiện hai truy vấn: một truy vấn để lấy tất cả các bài viết và một truy vấn để lấy tất cả các người dùng liên quan.
Ví Dụ 2: Ký hiệu
Giả sử bạn có cùng mối quan hệ hasMany giữa tác giả và sách và bạn cần liệt kê các tác giả cùng số lượng sách của mỗi tác giả.
// Controller
public function index()
{
$authors = Author::with('books')->get();
return view('authors.index', compact('authors'));
}
Và sau đó, trong tệp Blade, bạn thực hiện một vòng lặp foreach cho bảng:
@foreach($authors as $author)
<tr>
<td>{{ $author->name }}</td>
<td>{{ $author->books()->count() }}</td>
</tr>
@endforeach
Mọi thứ trông có vẻ hợp lý, đúng không? Nhưng nhìn vào dữ liệu Debugbar bên dưới.
Chúng ta đang sử dụng eager loading, Author::with('books')
, nhưng tại sao lại có nhiều truy vấn xảy ra như thế?
Bởi vì, trong Blade, $author->books()->count()
không load mối quan hệ từ bộ nhớ.
$author->books()
có nghĩa là PHƯƠNG THỨC của mối quan hệ$author->books
có nghĩa là DỮ LIỆU được eager loaded vào bộ nhớ
Vậy, phương pháp liên kết sẽ truy vấn cơ sở dữ liệu cho mỗi tác giả. Nhưng nếu bạn tải dữ liệu mà không có dấu ngoặc "()", nó sẽ thành công sử dụng dữ liệu được eager loading:
Vì vậy, hãy chú ý đến chính xác những gì bạn đang sử dụng - phương pháp quan hệ hay dữ liệu.
Lưu ý rằng trong ví dụ cụ thể này có một giải pháp thậm chí còn tốt hơn. Nếu bạn chỉ cần dữ liệu tổng hợp được tính toán của mối quan hệ, mà không cần mô hình đầy đủ, thì bạn chỉ nên tải các tổng hợp, như withCount:
// Controller:
$authors = Author::withCount('books')->get();
// Blade:
{{ $author->books_count }}
Kết quả sẽ chỉ có MỘT truy vấn đến cơ sở dữ liệu, thậm chí không phải là hai truy vấn. Và bộ nhớ cũng sẽ không bị "polluted" với dữ liệu quan hệ, do đó cũng tiết kiệm được một số RAM.
Ví Dụ 3: Mối quan hệ "ẩn" trong Accessor
Hãy lấy một ví dụ tương tự: danh sách các tác giả, với cột cho biết tác giả có hoạt động hay không: "Có" hoặc "Không". Hoạt động đó được xác định bởi việc tác giả có ít nhất một cuốn sách hay không và được tính như một accessor bên trong mô hình Author.
// Controller:
public function index()
{
$authors = Author::all();
return view('authors.index', compact('authors'));
}
// Blade file
@foreach($authors as $author)
<tr>
<td>{{ $author->name }}</td>
<td>{{ $author->is_active ? 'Yes' : 'No' }}</td>
</tr>
@endforeach
"is_active" được định nghĩa trong model Eloquent:
use Illuminate\Database\Eloquent\Casts\Attribute;
class Author extends Model
{
public function isActive(): Attribute
{
return Attribute::make(
get: fn () => $this->books->count() > 0,
);
}
}
Hãy xem Debugbar hiển thị những gì:
Đúng, chúng ta có thể giải quyết bằng eager loading các sách trong Controller. Nhưng trong trường hợp này, lời khuyên chung của tôi là tránh sử dụng các mối quan hệ trong accessor . Bởi vì accessor thường được sử dụng khi hiển thị dữ liệu và trong tương lai, người khác có thể sử dụng accessor này trong một số tệp Blade khác và bạn sẽ không kiểm soát được Controller đó trông như thế nào.
Nói cách khác, Accessor được cho là một phương pháp có thể tái sử dụng để định dạng dữ liệu, do đó bạn không kiểm soát được khi nào/cách thức dữ liệu sẽ được tái sử dụng. Trong trường hợp hiện tại của bạn, bạn có thể tránh truy vấn N+1, nhưng trong tương lai, người khác có thể không nghĩ đến nó.
# Giải pháp tích hợp chống lại truy vấn N + 1
Add eloquent strict loading mode
Danh mục
Bài viết liên quan

9 Mẹo Hữu Ích Khi Sử Dụng Blade Trong Laravel
Author: | ADMIN |
---|

Laravel Routing – 8 Advanced Tips
Author: | ADMIN |
---|

Một số lệnh Artisan Make với các tham số
Author: | ADMIN |
---|

Chinh phục triệu dòng dữ liệu: Các phương pháp tối ưu để nhập liệu hàng loạt trong Laravel
Author: | ADMIN |
---|
Bài viết khác

Blade Basics
Author: | ADMIN |
---|

Hiển thị giá trị trong Blade
Author: | ADMIN |
---|

Cấu Trúc Điều Kiện và Vòng Lặp Trong Blade
Author: | ADMIN |
---|