MySQL Query Optimization for Laravel Developers | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com/public) - [Home](https://www.msaied.com/public)
- [Projects](https://www.msaied.com/public/projects)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [About](https://www.msaied.com/public#about)

           [  Contact](https://www.msaied.com/public#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://www.msaied.com/public)
- [ProjectsCase studies](https://www.msaied.com/public/projects)
- [ArticlesEngineering notes](https://www.msaied.com/public/articles)
- [CertificatesCredentials](https://www.msaied.com/public/certificates)
- [AboutHow I work](https://www.msaied.com/public#about)
- [ContactGet in touch](https://www.msaied.com/public#contact)

  [Start a conversation](https://www.msaied.com/public#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://www.msaied.com/public)
2. /
3. [Articles](https://www.msaied.com/public/articles)
4. /
5. MySQL Optimization for Laravel: Covering Indexes, EXPLAIN ANALYZE, and Query Profiling

 MySQL Optimization for Laravel: Covering Indexes, EXPLAIN ANALYZE, and Query Profiling
=======================================================================================

 Stop guessing why your Laravel app slows down under load. Learn how to read EXPLAIN ANALYZE output, build covering indexes, and profile slow queries with real Eloquent examples.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://www.msaied.com/public#person) Published 30 Jun 2026 · Updated 30 Jun 2026 · 4 min read

ShareCopy linkCopied

 ![MySQL Optimization for Laravel: Covering Indexes, EXPLAIN ANALYZE, and Query Profiling](https://cdn.msaied.com/327/a63b6cb7e6fa83812746015ccb2ee7b7.png) 

  On this page +1. [Why Your Eloquent Queries Are Slower Than They Should Be](#why-your-eloquent-queries-are-slower-than-they-should-be)
2. [Reading EXPLAIN ANALYZE](#reading-explain-analyze)
3. [Covering Indexes: The Single Biggest Win](#covering-indexes-the-single-biggest-win)
4. [Profiling Slow Queries in Laravel](#profiling-slow-queries-in-laravel)
5. [Practical Checklist](#practical-checklist)
6. [Takeaways](#takeaways)

 Why Your Eloquent Queries Are Slower Than They Should Be
--------------------------------------------------------

Most Laravel performance problems trace back to one of three root causes: missing indexes, indexes that exist but are never used, or queries that fetch far more data than they need. The fix is rarely "add Redis" — it's understanding what MySQL is actually doing.

This article focuses on three concrete skills: reading `EXPLAIN ANALYZE` output, building covering indexes, and profiling slow queries in a Laravel context.

---

Reading EXPLAIN ANALYZE
-----------------------

MySQL 8.0+ supports `EXPLAIN ANALYZE`, which executes the query and returns real timing data alongside the estimated plan. Run it directly or via Laravel's query log:

```php
// Log the raw SQL, then run EXPLAIN ANALYZE in your DB client
$sql = User::where('status', 'active')
    ->where('created_at', '>=', now()->subDays(30))
    ->orderBy('created_at')
    ->toSql();

// Or use DB::select directly
$plan = DB::select('EXPLAIN ANALYZE ' . $sql, ['active', now()->subDays(30)]);

```

Key fields to watch:

- **type**: `ALL` means a full table scan. `ref` or `range` means an index is being used.
- **rows**: MySQL's estimate of rows examined. A high number relative to returned rows signals a poor index.
- **Extra**: `Using filesort` and `Using temporary` are red flags for ORDER BY and GROUP BY performance.
- **actual time**: In `EXPLAIN ANALYZE`, the `actual time=X..Y` values show real loop timing in milliseconds.

```sql
-> Index range scan on users using idx_status_created  (cost=120.5 rows=980)
   (actual time=0.412..3.201 rows=874 loops=1)

```

If `rows` is 50,000 but `actual rows` is 12, your index is working but the selectivity is poor — consider a more selective composite index.

---

Covering Indexes: The Single Biggest Win
----------------------------------------

A covering index includes every column the query needs, so MySQL never touches the actual table rows (no "heap fetch"). This is especially powerful for paginated list queries.

Consider a typical admin list:

```php
User::where('status', 'active')
    ->select('id', 'name', 'email', 'created_at')
    ->orderBy('created_at', 'desc')
    ->paginate(25);

```

A standard index on `status` forces MySQL to fetch the row for every match to retrieve `name`, `email`, and `created_at`. A covering index eliminates that:

```php
// In a migration
Schema::table('users', function (Blueprint $table) {
    $table->index(['status', 'created_at', 'name', 'email'], 'idx_users_covering_list');
});

```

Now `EXPLAIN` will show `Using index` in the Extra column — the query is satisfied entirely from the index B-tree.

**Rule of thumb**: put the equality columns first (`status`), then the range/sort column (`created_at`), then the projected columns.

---

Profiling Slow Queries in Laravel
---------------------------------

Enable the slow query log in MySQL (`long_query_time = 1`) and point `mysqldumpslow` at the log. For development, Laravel's built-in query listener is faster:

```php
// AppServiceProvider::boot()
if (app()->isLocal()) {
    DB::listen(function ($query) {
        if ($query->time > 100) { // ms
            logger()->warning('Slow query', [
                'sql'  => $query->sql,
                'time' => $query->time,
                'bindings' => $query->bindings,
            ]);
        }
    });
}

```

For production, use Laravel Telescope's query watcher or Pulse's slow query recorder — both surface the call stack so you can trace the Eloquent call site without grep.

---

Practical Checklist
-------------------

- **Always run `EXPLAIN ANALYZE`** before shipping a new query that touches large tables.
- **Composite index column order matters**: equality predicates first, range/sort last, projected columns after.
- **Covering indexes** eliminate heap fetches and are the highest-leverage optimization for read-heavy list endpoints.
- **`Using filesort` is not always fatal** — if the result set is small, MySQL sorts in memory quickly. It becomes a problem at scale.
- **`DB::listen`** in local/staging catches slow queries before they reach production.
- **Avoid `SELECT *`** in Eloquent — it prevents covering indexes from working and increases network payload.

---

Takeaways
---------

- `EXPLAIN ANALYZE` gives you real execution timing, not just estimates — use it on every non-trivial query.
- Covering indexes are the single most impactful optimization for paginated list queries in Laravel admin panels.
- Laravel's `DB::listen` and Telescope's query watcher are your first-line profiling tools before reaching for external APMs.
- Column order in composite indexes is not arbitrary — get it wrong and MySQL ignores the index entirely.

- [laravel](https://www.msaied.com/public/articles?search=laravel)
- [mysql](https://www.msaied.com/public/articles?search=mysql)
- [performance](https://www.msaied.com/public/articles?search=performance)
- [eloquent](https://www.msaied.com/public/articles?search=eloquent)
- [database](https://www.msaied.com/public/articles?search=database)

 Frequently asked questions 
---------------------------

  When should I use a covering index versus a regular composite index?Use a covering index when your query selects a small, fixed set of columns and runs frequently — such as paginated admin lists. If the SELECT columns change often or are numerous, a covering index becomes expensive to maintain and a regular composite index on the WHERE/ORDER BY columns is sufficient.

   Does EXPLAIN ANALYZE actually execute the query and affect production data?Yes — EXPLAIN ANALYZE runs the query for real to collect actual timing. For SELECT queries this is safe. Never run EXPLAIN ANALYZE on INSERT, UPDATE, or DELETE in production without wrapping it in a transaction you immediately roll back.

   How do I find which Eloquent model method is generating a slow query in production?Laravel Telescope captures the full stack trace alongside each query. In production without Telescope, add a DB::listen callback that logs slow queries with debug\_backtrace(DEBUG\_BACKTRACE\_IGNORE\_ARGS, 10) to pinpoint the call site.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

[Mohamed Said](https://www.msaied.com/public#person)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

[About](https://www.msaied.com/public#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articleClonio CLI: Clone Production Databases With Anonymized Data](https://www.msaied.com/public/articles/clonio-cli-clone-production-databases-with-anonymized-data) [Next articlePostgreSQL Full-Text Search in Laravel: Indexes, Ranking, and Multilingual Queries](https://www.msaied.com/public/articles/postgresql-full-text-search-in-laravel-indexes-ranking-and-multilingual-queries-1)  

   On this page
-------------

1. [Why Your Eloquent Queries Are Slower Than They Should Be](#why-your-eloquent-queries-are-slower-than-they-should-be)
2. [Reading EXPLAIN ANALYZE](#reading-explain-analyze)
3. [Covering Indexes: The Single Biggest Win](#covering-indexes-the-single-biggest-win)
4. [Profiling Slow Queries in Laravel](#profiling-slow-queries-in-laravel)
5. [Practical Checklist](#practical-checklist)
6. [Takeaways](#takeaways)

 ###  Have a technical challenge?

 Tell me what you’re building. I reply within two working days.

[Start a conversation](https://www.msaied.com/public#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/740/cce86edc21eddcbdd2f2454fadaf9c70.png)  · 3 min read### The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware

5 Oct 2026 ](https://www.msaied.com/public/articles/the-pipeline-pattern-in-laravel-custom-pipelines-beyond-middleware-1) [ ![](https://cdn.msaied.com/739/2d6897fdcdcf090613f96f72a64b8a78.png)  · 4 min read### MySQL Full-Text Search in Laravel: Indexes, Relevance Scoring, and Boolean Mode

4 Oct 2026 ](https://www.msaied.com/public/articles/mysql-full-text-search-in-laravel-indexes-relevance-scoring-and-boolean-mode) [ ![](https://cdn.msaied.com/738/073696a3fefe18bec825beec5ac658f5.png)  · 4 min read### Laravel Queue Rate-Limited Middleware: Throttling Jobs Without Losing Work

4 Oct 2026 ](https://www.msaied.com/public/articles/laravel-queue-rate-limited-middleware-throttling-jobs-without-losing-work) 

  Have a technical challenge?
----------------------------

Tell me what you’re building. I reply within two working days.

 [Discuss your project ↗](https://www.msaied.com/public#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://www.msaied.com/public)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://www.msaied.com/public/sitemap.xml)
