N+1 виникає, коли ви завантажуєте N моделей одним запитом, а потім у циклі звертаєтесь до їхнього зв'язку - це генерує ще N запитів.
$posts = Post::all(); // 1 запит
foreach ($posts as $post) {
echo $post->author->name; // +1 запит на кожен пост → N запитів
}
Рішення - eager loading через with():
$posts = Post::with('author')->get(); // лише 2 запити загалом
Вкладені та умовні зв'язки:
Post::with(['author', 'comments.user'])->get(); // вкладений eager
Post::with(['comments' => fn ($q) => $q->latest()])->get(); // умовний
Лічильники без завантаження зв'язку - withCount() (без N+1 і без вантаження самих рядків):
$posts = Post::withCount('comments')->get(); // доступ через $post->comments_count
Виявлення: Model::preventLazyLoading(! app()->isProduction()) у boot() кидає виняток на ледачих завантаженнях поза продакшеном; також допомагають Telescope і Debugbar.