Laravel Eloquent Query to 2 Million Rows takes long time

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Optimizing Laravel Eloquent Queries on Massive Datasets: Solving the 15-Second Bottleneck

As a senior developer working with large-scale applications, we frequently encounter performance bottlenecks when dealing with massive database tables. The scenario you described—an Eloquent query taking over 15 seconds to execute across a 2 million row table—is a classic challenge that highlights the difference between code that works and code that is truly performant.

This post will dissect why your whereIn query might be slowing down, and provide concrete, actionable strategies to optimize data retrieval in Laravel, ensuring your API remains snappy even as your database scales.

Diagnosing the Bottleneck: Why Eloquent Slows Down

Your initial query looks clean, leveraging Eloquent’s expressive power:

$imagesData = Images::whereIn('file_id', $fileIds)
                    ->with('image.user')
                    ->with('file')
                    ->orderBy('created_at', 'DESC')
                    ->simplePaginate(12);

When dealing with millions of rows, the bottleneck rarely lies in the Eloquent syntax itself, but rather in how the database engine executes the complex operation, especially when combined with eager loading (with).

The whereIn clause forces the database to scan a potentially massive index or table to find all matching primary keys. When you add multiple eager loads across relationships (like image.user and file), the cost of fetching and joining this immense result set multiplies significantly, leading to slow response times. The time spent is often dominated by disk I/O and memory management during the JOIN operations, rather than simple filtering.

Optimization Strategy 1: Foundation First – Indexing

Before rewriting the query logic, we must ensure the foundation is solid. This is the most crucial step in any database performance improvement.

Ensure Proper Indexing: For whereIn clauses to be fast, the columns involved must be properly indexed. If you are querying based on file_id, the file_id column must have an index. Furthermore, since you are sorting by created_at, a composite index on (file_id, created_at) can further optimize the retrieval process.

If you are using MariaDB with InnoDB, ensure your indexes cover the columns used in filtering and ordering. This foundational step is vital for efficient data access, regardless of how elegant your Laravel code is.

Optimization Strategy 2: Refining Eloquent Queries

If indexing isn't enough, we need to reduce the amount of data the database has to process before it hits the application layer.

A. Select Only Necessary Columns

Eager loading fetches all columns for the related models by default. If you only need a few fields from the images table and their relations, explicitly selecting only those columns can drastically reduce the payload size and I/O operations.

$imagesData = Images::whereIn('file_id', $fileIds)
    ->select('id', 'file_id', 'created_at') // Select only necessary columns from the main table
    ->with(['image' => function ($query) {
        $query->select('id', 'user_id'); // Only select needed fields from related tables
    }, 'file' => function ($query) {
        $query->select('id', 'filename');
    }])
    ->orderBy('created_at', 'DESC')
    ->simplePaginate(12);

B. Leveraging Raw SQL for Complex Filtering (The Power of whereRaw)

When Eloquent’s abstraction layer proves too slow for highly complex filtering or massive set operations, stepping down to raw SQL can provide the necessary control over the execution plan. The suggestion to use DB::whereRaw is often valid when dealing with extreme scale, as it allows you to bypass potential overhead in relationship hydration and execute a highly optimized query directly.

Instead of relying solely on Eloquent's high-level methods for this specific bottleneck, you can construct the filtering logic using raw SQL constructs which sometimes yield superior performance:

$sql = "SELECT * FROM images WHERE file_id IN (" . implode(',', $fileIds) . ") ORDER BY created_at DESC LIMIT 12 OFFSET 0";

// Execute the query directly
$imagesData = DB::select($sql);

While this trades some of Eloquent's syntactic sugar, it forces the database to execute a single, highly focused operation, which can be significantly faster when dealing with millions of rows. Remember, understanding the underlying mechanics is key to mastering any framework like Laravel, as we strive to build robust and efficient systems, much like the architecture principles discussed at laravelcompany.com.

Conclusion

Optimizing slow queries on large tables is an iterative process that requires a holistic view: it starts with database indexing, moves to refining your Eloquent selects, and sometimes culminates in leveraging raw SQL for maximum performance control. By applying these principles—indexing the necessary columns, selecting only what you need, and knowing when to use lower-level tools like whereRaw—you can transform those frustrating 15-second waits into instantaneous API responses.