Laravel’s Eloquent ORM simplifies database interactions with an expressive syntax, making it one of the easiest tools for managing models and relationships in PHP frameworks. However, inefficient use of Eloquent can lead to performance bottlenecks, such as excessive queries, memory overuse, and slow response times.

With Laravel 12, new features and enhancements have been introduced to optimize Eloquent performance. These updates make writing faster, more efficient queries easier while keeping your application scalable. In this guide, we’ll explore techniques like eager loading, indexing, query optimization, and caching, using an online office furniture store as a case study.

Let’s unlock the full potential of your Laravel 12 application! 🚀

1. Common Eloquent Performance Bottlenecks

Here are common issues that can impact Eloquent performance:

  • 🔴 N+1 Query Problem — Running multiple queries when fetching related data.
  • 🔴 Large Dataset Retrieval — Fetching too many records, leading to memory issues.
  • 🔴 Unoptimized Queries — Using inefficient relationships, queries, or indexes.
  • 🔴 Excessive Model Instantiation — Creating unnecessary Eloquent models.
  • 🔴 Lack of Database Indexing — Slowing down searches and filtering.

2. Optimizing Query Execution

Pattern 1: Eager Loading to Prevent N+1 Queries

Laravel 12 introduces lazy eager loading improvements, allowing relationships to load dynamically only when needed.

Bad Practice (Lazy Loading)
$orders = Order::all();  
foreach ($orders as $order) {  
    echo $order->customer->name;  // Runs a separate query for each order!
}

👎 Issue: 101 queries for 100 orders (1 for orders + 100 for customers).

Optimized Practice (Eager Loading)
$orders = Order::with('customer')->get();  
foreach ($orders as $order) {  
    echo $order->customer->name;  // Only 2 queries (1 for orders, 1 for customers)
}

Result: Drastically reduces database queries.

Pattern 2: Select Specific Columns (Avoid SELECT *)

Fetching unnecessary columns increases memory usage. Laravel 12 supports column selection in eager loading relationships.

Optimized Practice

$orders = Order::with('customer:id,name')->select('id', 'customer_id', 'total')->get();

Result: Faster execution and lower memory usage.

Pattern 3: Chunking for Large Datasets

Fetching all records at once can cause memory overflow. Laravel 12 enhances chunking with parallel processing support.

Optimized Practice

Order::chunk(500, function ($orders) {
    foreach ($orders as $order) {
        // Process orders in batches of 500
    }
});

Result: Efficiently handles large datasets.

Pattern 4: Caching Queries for Repeated Calls

Laravel 12 introduces query cache tags, enabling better grouping and invalidation of cached queries.

Optimized Practice

$orders = Cache::tags(['orders'])->remember('latest_orders', 60, function () {
    return Order::latest()->take(10)->get();
});

Result: Reduces database hits and improves performance.

3. Optimizing Relationships in Eloquent

Pattern 5: Indexing One-to-Many Relationships

For relationships like “Customer has many Orders”, indexing foreign keys improves query speed.

Optimized Practice

Schema::create('orders', function (Blueprint $table) {
    $table->foreignId('customer_id')->constrained()->index();
});

Result: Faster filtering by `customer_id`.

Pattern 6: Counting Relationships Efficiently

Laravel 12 introduces lazy relationship counts, deferring counting until explicitly needed.

Optimized Practice

$customer = Customer::withCount('orders')->find(1);
echo $customer->orders_count;

Result: Performs COUNT() at the database level for efficiency.

4. Query Optimization Techniques

Pattern 7: Indexing Frequently Queried Columns

Indexes significantly improve query speed for frequently filtered columns.

Optimized Practice

Schema::table('orders', function (Blueprint $table) {
    $table->index('status');  
});

Result: Faster searches by `status`.

Pattern 8: Avoiding ORDER BY RAND() for Random Records

Bad Practice

$randomProduct = Product::inRandomOrder()->first();

👎 Issue: Slow on large tables.

Optimized Practice

$randomProduct = Product::where('id', '>=', DB::raw('(SELECT FLOOR( MAX(id) * RAND() ) FROM products)'))->first();

Result: Efficient random selection for large datasets.

5. Lazy Loading vs. Eager Loading vs. Query Joins

  • Lazy Loading: Fetches related records only when accessed, leading to multiple queries (N+1 problem).
  • Eager Loading (`with()`): Fetches related records upfront in fewer queries, ideal for complex reports.
  • Query Joins (`join()`): Retrieves data in a single SQL query without creating model instances, offering maximum performance.

Example of Using Joins

$orders = Order::join('customers', 'orders.customer_id', '=', 'customers.id')
       ->select('orders.id', 'orders.total', 'customers.name')
       ->get();

Result: Faster than Eloquent relationships for reports and analytics.

Conclusion: Best Practices Summary

  • ✅ Use eager loading (`with()`) to prevent N+1 queries.
  • ✅ Select only required columns instead of `SELECT *`.
  • ✅ Use `chunk()` for large datasets to avoid memory overload.
  • ✅ Cache queries to reduce repeated database hits.
  • ✅ Use `withCount()` for efficient relationship counts.
  • ✅ Add indexes to foreign keys and frequently queried columns.
  • ✅ Use joins (`join()`) for reports instead of Eloquent relationships.

By implementing these Eloquent performance patterns and leveraging Laravel 12’s new features, you can optimize your application for high performance. Whether it’s an online office furniture store or any other system, these techniques will make your Laravel app blazing fast. 🚀

Leave a Reply

Your email address will not be published. Required fields are marked *

Attach images – Only PNG, JPG, JPEG and GIF are supported.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Quote of the week

"People ask me what I do in the winter when there's no baseball. I'll tell you what I do. I stare out the window and wait for spring."

~ Rogers Hornsby

All rights resolved