Ví dụ về các vấn đề truy vấn N+1
15395

# 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.

# Laravel Debugbar

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


  1. Khác
  2. ThreeJS
  3. Ubuntu/Linux
  4. HTML/CSS
  5. Git
  6. Amazon Web Services
  7. Javascript
  8. Docker
  9. Laravel

Bài viết liên quan


9 Mẹo Hữu Ích Khi Sử Dụng Blade Trong Laravel

9 Mẹo Hữu Ích Khi Sử Dụng Blade Trong Laravel

01.08.2024
Author: ADMIN
Khám phá 9 mẹo Blade giúp bạn viết code Laravel sạch, tối ưu và chuyên nghiệp hơn. Từ @forelse, @auth, @guest, đến format ngày, tối ưu SEO – tất cả trong một bài viết súc tích, dễ áp dụng!
Laravel Routing – 8 Advanced Tips

Laravel Routing – 8 Advanced Tips

01.08.2024
Author: ADMIN
Laravel Routing không chỉ là Route::get(). Khám phá 8 mẹo nâng cao giúp bạn kiểm soát route tốt hơn, tối ưu API, subdomain, rate limit và caching! 🚀
Một số lệnh Artisan Make với các tham số

Một số lệnh Artisan Make với các tham số

01.08.2024
Author: ADMIN
Tổng hợp các lệnh php artisan make quan trọng trong Laravel giúp bạn tối ưu hóa quy trình phát triển! 🚀💡
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

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

16.02.2025
Author: ADMIN
Khám phá 10 phương pháp tối ưu nhập liệu hàng loạt trong Laravel, từ cơ bản đến nâng cao (Chunk, Lazy Collection, PDO, LOAD DATA INFILE). So sánh hiệu suất, bộ nhớ, và số lượng truy vấn để chọn giải pháp tốt nhất cho nhu cầu của bạn.

Bài viết khác

Routing

Routing

01.08.2024
Author: ADMIN
Hướng dẫn chi tiết về Basic Routing trong Laravel, từ cách định nghĩa route, sử dụng middleware, route caching đến route naming giúp tối ưu hóa ứng dụng.
Blade Basics

Blade Basics

01.08.2024
Author: ADMIN
Khám phá Blade trong Laravel: từ if-else, loops, kế thừa layout đến include sub-views. Giúp code gọn gàng, dễ quản lý và bảo trì hơn!
Hiển thị giá trị trong Blade

Hiển thị giá trị trong Blade

01.08.2024
Author: ADMIN
Hướng dẫn hiển thị biến trong Laravel Blade: escape HTML tự động, hiển thị dữ liệu thô, giá trị mặc định và cách truy xuất mảng, đối tượng. Giúp bạn tối ưu hiển thị dữ liệu một cách an toàn!
Cấu Trúc Điều Kiện và Vòng Lặp Trong Blade

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

01.08.2024
Author: ADMIN
Khám phá các cấu trúc điều kiện và vòng lặp trong Laravel Blade. Tận dụng @if, @foreach, @forelse để hiển thị dữ liệu linh hoạt, giúp mã nguồn dễ đọc, sạch sẽ và tối ưu hơn!