Header Ads

Header ADS

Laravel-এ N+1 Query Problem Solve

 Laravel-এ N+1 Query Problem একটি সাধারণ পারফরম্যান্স সমস্যা যা ডেটাবেইস থেকে ডেটা রিট্রাইভ করার সময় দেখা যায়, বিশেষ করে যখন রিলেশনাল ডেটা ব্যবহৃত হয় (যেমন: hasMany, belongsTo ইত্যাদি)। নিচে বাংলায় বিস্তারিত ব্যাখ্যা করছি:


🧠 কী বোঝায় N+1 Query Problem?

N+1 Query Problem তখন ঘটে যখন আপনি একটি মূল মডেলের N টি রেকর্ড আনছেন, এবং প্রতিটি রেকর্ডের সাথে সম্পর্কিত ডেটা (রিলেশনশিপ) আলাদাভাবে কোয়েরি করে আনছেন।

🧾 উদাহরণ:

ধরা যাক, আপনার দুটি মডেল আছে:

  • Post

  • Comment (একটি পোস্টের অনেক কমেন্ট থাকতে পারে — hasMany সম্পর্ক)

আপনি যদি নিচের মতো কোড ব্যবহার করেন:


$posts = Post::all(); foreach ($posts as $post) { echo $post->comments->count(); }

কি হচ্ছে এখানে:

  1. প্রথম লাইনে Post::all() — একটি কোয়েরিতে সব পোস্ট নিয়ে আসছে। (১টি কোয়েরি)

  2. তারপর প্রতিটি পোস্টের জন্য $post->comments — আলাদাভাবে কমেন্টগুলো নিচ্ছে। যদি ১০টি পোস্ট থাকে, তাহলে এখানে ১০টি আলাদা SQL কোয়েরি হবে।

👉 মোট কোয়েরি = 1 (Post fetch) + 10 (each Post’s Comments) = 11 কোয়েরি

এটাই হলো N+1 Query Problem। যেখানে N = 10, তাই হয়েছে 1 + N = 11 কোয়েরি।


🚀 সমাধান: Eager Loading

Laravel আমাদেরকে এই সমস্যার সমাধানে Eager Loading দেয়, যার মাধ্যমে আমরা একসাথে রিলেটেড ডেটা নিয়ে আসতে পারি।


$posts = Post::with('comments')->get(); foreach ($posts as $post) { echo $post->comments->count(); }

এখানে Laravel দুটি কোয়েরিতে সব পোস্ট এবং তাদের কমেন্টগুলো নিয়ে আসবে:

  1. SELECT * FROM posts

  2. SELECT * FROM comments WHERE post_id IN (...)

👉 মোট কোয়েরি = 2


একটি ই-কমার্স সাইট

🎯 মডেলসমূহ:

  1. Order

  2. Customer

  3. OrderItem

  4. Product

⚙️ সম্পর্কগুলো:

  • এক Customer-এর অনেকগুলো Order

  • এক Order-এর অনেকগুলো OrderItem

  • প্রতিটি OrderItem একটি Product-এর সাথে যুক্ত


❌ N+1 Problem Example:


$orders = Order::all(); foreach ($orders as $order) { echo "Customer Name: " . $order->customer->name; foreach ($order->orderItems as $item) { echo "Product: " . $item->product->name; } }

👉 এখানে কী হচ্ছে?

  • Order::all() — সব অর্ডার আনছে (ধরা যাক 100টি অর্ডার) → 1 কোয়েরি

  • প্রতিটি $order->customer কল করলে 100টি আলাদা কোয়েরি হবে → 100 কোয়েরি

  • প্রতিটি $order->orderItems কল করলে আবার 100 কোয়েরি → 100 কোয়েরি

  • প্রতিটি $item->product কল করলে যদি 500 আইটেম থাকে, তাহলে 500 কোয়েরি → 500 কোয়েরি

মোট = 1 + 100 + 100 + 500 = 701 কোয়েরি 😱


✅ সমাধান: Eager Loading দিয়ে


$orders = Order::with(['customer', 'orderItems.product'])->get(); foreach ($orders as $order) { echo "Customer Name: " . $order->customer->name; foreach ($order->orderItems as $item) { echo "Product: " . $item->product->name; } }

👉 এখানে Laravel নিচের মত করে কাজ করবে:

  1. SELECT * FROM orders → 1 কোয়েরি

  2. SELECT * FROM customers WHERE id IN (...) → 1 কোয়েরি

  3. SELECT * FROM order_items WHERE order_id IN (...) → 1 কোয়েরি

  4. SELECT * FROM products WHERE id IN (...) → 1 কোয়েরি

মোট = 4 কোয়েরি ✅


✅ আরও একটি বাস্তব উদাহরণ: ব্লগ সাইট

মডেলসমূহ:

  • Post

  • User

  • Category

  • Tag


$posts = Post::with(['user', 'category', 'tags'])->latest()->get(); foreach ($posts as $post) { echo $post->title; echo $post->user->name; echo $post->category->name; foreach ($post->tags as $tag) { echo $tag->name; } }

Laravel এখানে চারটি Eager Load কোয়েরির মাধ্যমে সব রিলেশন লোড করবে।


📌 উপসংহার:

বিষয়খারাপ পদ্ধতিভালো পদ্ধতি
Customer Load$order->customer বারবারwith('customer')
Items Load$order->orderItems বারবারwith('orderItems')
Nested Load$item->product বারবারwith('orderItems.product')
 

🛠 পরামর্শ:

  • Laravel Debugbar বা Telescope দিয়ে কোয়েরির সংখ্যা পর্যবেক্ষণ করুন।

  • সর্বদা large loop এর আগে Eager Load করুন।

  • প্রয়োজনে Lazy Eager Loading (load()) ব্যবহার করুন যদি পরবর্তী ধাপে লোড করতে চান।




-------------------------------------

Laravel Query Builder ব্যবহার করে N+1 problem-এর একটি বাস্তব উদাহরণ এবং তার সমাধান দিচ্ছি।


🎯 ধরুন আমাদের দুইটি টেবিল আছে:

  • categories

  • products (প্রতিটি প্রোডাক্ট একটি ক্যাটেগরির অন্তর্ভুক্ত — products.category_id)


❌ N+1 Problem (Query Builder দিয়ে)


$products = DB::table('products')->get(); foreach ($products as $product) { $category = DB::table('categories')->where('id', $product->category_id)->first(); echo $product->name . ' - ' . $category->name . "<br>"; }

🔎 এখানে কী হচ্ছে?

  • প্রথমে সব products আনছে → ১টি কোয়েরি

  • তারপর প্রতিটি product এর জন্য আলাদা করে ক্যাটেগরি আনছে → ধরুন ১০০টা প্রোডাক্ট হলে ১০০টি কোয়েরি

মোট কোয়েরি = 1 + 100 = 101 ❌


✅ সমাধান (Eager Loading এর মতো Query Builder ব্যবহার করে JOIN)


$products = DB::table('products') ->join('categories', 'products.category_id', '=', 'categories.id') ->select('products.*', 'categories.name as category_name') ->get(); foreach ($products as $product) { echo $product->name . ' - ' . $product->category_name . "<br>"; }

✅ এখানে কী হচ্ছে?

  • আমরা JOIN ব্যবহার করে একবারেই প্রোডাক্ট এবং তার ক্যাটেগরি নিয়ে আসছি।

  • শুধুমাত্র ১টি কোয়েরি হচ্ছে!

মোট কোয়েরি = 1 ✅


✅ আরেকটি উদাহরণ: users এবং posts

❌ N+1 Problem (Query Builder)


$posts = DB::table('posts')->get(); foreach ($posts as $post) { $user = DB::table('users')->where('id', $post->user_id)->first(); echo $post->title . ' by ' . $user->name . "<br>"; }

মোট কোয়েরি = 1 + N (number of posts)


✅ সমাধান (JOIN দিয়ে)


$posts = DB::table('posts') ->join('users', 'posts.user_id', '=', 'users.id') ->select('posts.*', 'users.name as author_name') ->get(); foreach ($posts as $post) { echo $post->title . ' by ' . $post->author_name . "<br>"; }

মোট কোয়েরি = 1 ✅


🔚 উপসংহার

ধরনখারাপ পদ্ধতি (N+1)ভালো পদ্ধতি (JOIN)
products → categoryপ্রতি লুপে DB::table()->where()JOIN দিয়ে একবারে আনুন
posts → userপ্রতি লুপে ইউজার কলJOIN দিয়ে select করুন



-------------------------------------


কমপ্লেক্সবড় প্রজেক্ট ভিত্তিক উদাহরণ দিব, যাতে বুঝতে পারেন যে বড় অ্যাপ্লিকেশনে N+1 প্রোবলেম কিভাবে দেখা দেয় এবং কিভাবে তার সমাধান করা হয়।


🏨 উদাহরণ: Hotel Booking System

📦 মডেল সমূহ:

  • Hotel

  • Room

  • Booking

  • User (Customer)

সম্পর্ক:

  • একটি Hotel-এর অনেকগুলো Room

  • প্রতিটি Room-এ অনেক Booking

  • প্রতিটি Booking একজন User দ্বারা করা


❌ N+1 Problem (Eloquent)

php
$hotels = Hotel::all(); foreach ($hotels as $hotel) { echo "Hotel: " . $hotel->name . "<br>"; foreach ($hotel->rooms as $room) { echo "Room: " . $room->room_number . "<br>"; foreach ($room->bookings as $booking) { echo "Booked by: " . $booking->user->name . "<br>"; } } }

👉 ধরা যাক:

  • 10টা হোটেল

  • প্রতিটিতে 20টা রুম → 200টি রুম

  • প্রতিটি রুমে 5টা বুকিং → 1000টি বুকিং

  • প্রতিটি বুকিং-এ ইউজার লোড হচ্ছে

মোট কোয়েরি:

  • Hotel::all() → 1

  • প্রতিটি হোটেলের জন্য rooms → 10 কোয়েরি

  • প্রতিটি রুমের জন্য bookings → 200 কোয়েরি

  • প্রতিটি বুকিং এর জন্য user → 1000 কোয়েরি

👉 মোট = 1 + 10 + 200 + 1000 = 1211 কোয়েরি ❌


✅ সমাধান: Eager Loading

php
$hotels = Hotel::with('rooms.bookings.user')->get(); foreach ($hotels as $hotel) { echo "Hotel: " . $hotel->name . "<br>"; foreach ($hotel->rooms as $room) { echo "Room: " . $room->room_number . "<br>"; foreach ($room->bookings as $booking) { echo "Booked by: " . $booking->user->name . "<br>"; } } }

✅ Laravel করবে:

  1. hotels টেনে আনবে → 1 কোয়েরি

  2. সব rooms টেনে আনবে (hotel_id IN (...)) → 1 কোয়েরি

  3. সব bookings টেনে আনবে (room_id IN (...)) → 1 কোয়েরি

  4. সব users টেনে আনবে (user_id IN (...)) → 1 কোয়েরি

👉 মোট = 4 কোয়েরি ✅


🔁 Query Builder দিয়ে (JOINs):

php
$results = DB::table('hotels') ->join('rooms', 'hotels.id', '=', 'rooms.hotel_id') ->join('bookings', 'rooms.id', '=', 'bookings.room_id') ->join('users', 'bookings.user_id', '=', 'users.id') ->select( 'hotels.name as hotel_name', 'rooms.room_number', 'users.name as customer_name' ) ->get(); foreach ($results as $row) { echo "Hotel: $row->hotel_name | Room: $row->room_number | Booked by: $row->customer_name<br>"; }

👉 এখানে শুধুমাত্র ১টি JOIN কোয়েরি দিয়ে আমরা সব কিছু পেলাম।


✅ Laravel Tips:

  • অনেক লেভেলের রিলেশন থাকলে: with('a.b.c')

  • Conditional Eager Loading: with(['comments' => function($q) { $q->where('status', 'approved'); }])

  • Lazy Eager Loading: $model->load('relation')









No comments

Theme images by fpm. Powered by Blogger.