Back to Blog

Eloquent Performance Tricks: Making Laravel Fly with Thousands of Records

2024-02-05 * 9 min read min Web Development
When you first start with Laravel, Eloquent feels like magic. A few lines of code, and you can query your database with beautiful, expressive syntax. But as your project grows and you start handling thousands (or even millions) of rows, that magic can suddenly turn into slow queries, memory spikes, and lagging pages. The good news? Laravel's Eloquent ORM can scale really well - if you know a few performance tricks. In this article, I'll share the techniques that helped me and my team optimize large-scale applications without giving up the elegance of Eloquent.

1. Use select() to Retrieve Only What You Need

By default, Eloquent retrieves all columns (SELECT *) from your database. For small tables, that's fine. But with big tables and wide schemas, this wastes memory and slows down queries.
// ❌ Avoid: pulls all columns
$users = User::all();

// ✅ Better: fetch only the needed fields
$users = User::select('id', 'name', 'email')->get();
👉 This small change can save megabytes of memory in large queries.

2. Chunk Large Queries

When you need to process thousands of records, don't load them all at once. Use chunking to process them in batches.
User::chunk(500, function ($users) {
    foreach ($users as $user) {
        // process user
    }
});
Instead of pulling 100k rows into memory, this grabs 500 at a time. Laravel takes care of pagination behind the scenes. If order matters (e.g., for consistent batch processing), don't forget to sort by id.

3. Use lazy() or cursor() for Streaming

If you're working with really huge datasets, chunking may still consume too much memory. In that case, use cursors:
foreach (User::cursor() as $user) {
    // Stream through records one by one
}
This avoids loading large chunks into memory, streaming rows directly from the database. It's slower per row but extremely memory-efficient.

4. Eager Load Relationships (but Don't Overload)

The N+1 query problem is one of the biggest performance killers in Laravel apps.
// ❌ Bad: runs a new query for each post
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->user->name;
}

// ✅ Good: eager load users
$posts = Post::with('user')->get();
This reduces dozens of queries into just two. ⚠️ But don't eager-load everything. Only load the relationships you really need, otherwise you'll overwhelm memory with unused data.

5. Index Your Database Columns

Sometimes, the bottleneck isn't Eloquent - it's the database itself. Adding proper indexes can cut query times from seconds to milliseconds.
Schema::table('users', function (Blueprint $table) {
    $table->index('email');
    $table->index(['status', 'created_at']);
});
👉 Always index columns that are frequently used in WHERE, ORDER BY, or JOIN clauses.

6. Paginate Instead of Fetching Everything

For lists and dashboards, never load thousands of records at once. Instead, paginate them.
$users = User::paginate(50);
This gives you clean pagination links and keeps your memory usage tiny. If you need infinite scrolling, simplePaginate() is even faster.

7. Use update() and delete() in Bulk

If you need to update or delete a lot of records, don't loop through them. Do it in one query.
// ❌ Bad: updates row by row
foreach ($users as $user) {
    $user->update(['active' => false]);
}

// ✅ Good: single bulk update
User::where('active', true)->update(['active' => false]);
This avoids thousands of queries and makes your app fly.

8. Cache When It Makes Sense

If you're repeatedly running the same heavy query, consider caching the results.
$users = Cache::remember('active_users', 600, function () {
    return User::where('active', true)->get();
});
Laravel makes this easy, and caching can turn a slow DB query into a fast memory lookup.

9. Profiling Queries

Don't guess where the bottlenecks are. Use the DB::enableQueryLog() or Laravel Telescope to profile queries.
DB::enableQueryLog();
$users = User::where('status', 'active')->get();
dd(DB::getQueryLog());
Or use toSql() to see what Eloquent generates:
dd(User::where('status', 'active')->toSql());

10. When Needed... Drop Down to Raw Queries

Eloquent is elegant, but sometimes you need raw performance. Laravel makes it easy to fall back to the query builder or even raw SQL.
// Query builder
$users = DB::table('users')->where('status', 'active')->get();

// Raw SQL
$users = DB::select('SELECT id, name FROM users WHERE status = ?', ['active']);
Mixing approaches is not a failure - it's pragmatic engineering.

Conclusion

Laravel's Eloquent ORM is not just for small projects. With the right tricks - from chunking and cursors, to eager loading and bulk updates - you can handle millions of rows efficiently without giving up the readability and maintainability that makes Laravel special. At the end of the day, performance optimization in Laravel isn't about abandoning Eloquent. It's about understanding its strengths, avoiding its pitfalls, and knowing when to mix in raw power. And yes, even after all these years, I still love coding with Laravel. It's grown with me from my first side projects to my role as CTO today. Just like PHP itself, it refuses to die - and honestly, I'm fine with that. 🚀