MySQL EXPLAIN for Laravel: Read Query Plans Fast | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com) [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles  ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

  [ home ](https://msaied.com)    [ articles ](https://msaied.com/articles)    MySQL EXPLAIN Demystified: Reading Query Plans to Kill Slow Laravel Queries        On this page       1. [  Why Guessing Doesn't Scale ](#why-guessing-doesnt-scale)
2. [  Getting EXPLAIN Output Inside Laravel ](#getting-explain-output-inside-laravel)
3. [  The Columns That Actually Matter ](#the-columns-that-actually-matter)
4. [  A Real-World Example: The Composite Index Fix ](#a-real-world-example-the-composite-index-fix)
5. [  When a Covering Index Goes Further ](#when-a-covering-index-goes-further)
6. [  Automating Detection in CI ](#automating-detection-in-ci)
7. [  Takeaways ](#takeaways)

  ![MySQL EXPLAIN Demystified: Reading Query Plans to Kill Slow Laravel Queries](https://cdn.msaied.com/202/c5931287aff833589e056e3629e95580.png)

  #laravel   #mysql   #performance   #eloquent  

 MySQL EXPLAIN Demystified: Reading Query Plans to Kill Slow Laravel Queries 
=============================================================================

     15 Jun 2026      3 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   Why Guessing Doesn't Scale  ](#why-guessing-doesnt-scale)
2. [  02   Getting EXPLAIN Output Inside Laravel  ](#getting-explain-output-inside-laravel)
3. [  03   The Columns That Actually Matter  ](#the-columns-that-actually-matter)
4. [  04   A Real-World Example: The Composite Index Fix  ](#a-real-world-example-the-composite-index-fix)
5. [  05   When a Covering Index Goes Further  ](#when-a-covering-index-goes-further)
6. [  06   Automating Detection in CI  ](#automating-detection-in-ci)
7. [  07   Takeaways  ](#takeaways)

 Why Guessing Doesn't Scale
--------------------------

Most Laravel developers reach for an index when a query feels slow, add one, and hope for the best. That workflow is fragile. MySQL's `EXPLAIN` statement tells you *exactly* what the optimizer decided to do — which index it chose, how many rows it expects to examine, and where it gave up and scanned the whole table. Reading it fluently is a force-multiplier skill.

Getting EXPLAIN Output Inside Laravel
-------------------------------------

You don't need a separate MySQL client. Wrap any Eloquent query with a quick macro or just use `DB::select`:

```php
// Quick one-off during local debugging
$sql = User::where('tenant_id', 42)
    ->where('status', 'active')
    ->orderBy('created_at')
    ->toRawSql(); // Laravel 10.15+

$plan = DB::select('EXPLAIN ' . $sql);
dd($plan);

```

For `EXPLAIN ANALYZE` (MySQL 8.0.18+, returns actual row counts and timing):

```php
$plan = DB::select(
    'EXPLAIN ANALYZE SELECT * FROM users WHERE tenant_id = ? AND status = ? ORDER BY created_at',
    [42, 'active']
);

```

`EXPLAIN ANALYZE` runs the query for real, so use it on a staging replica, not production under load.

The Columns That Actually Matter
--------------------------------

| Column | What to watch for | |---|---| | `type` | `ALL` = full scan (bad). Aim for `ref`, `range`, or `eq_ref`. | | `key` | `NULL` means no index was used. | | `rows` | Estimated rows examined — multiply across joined tables. | | `Extra` | `Using filesort` or `Using temporary` signals expensive post-processing. |

A `type: ALL` with `rows: 800000` on a joined table is the single most actionable red flag you will encounter.

A Real-World Example: The Composite Index Fix
---------------------------------------------

Consider this Eloquent scope that powers a Filament table:

```php
Order::query()
    ->where('tenant_id', $tenantId)
    ->where('status', 'pending')
    ->orderBy('created_at')
    ->paginate(25);

```

EXPLAIN shows `type: ref` on a single-column `tenant_id` index, but `Extra: Using filesort` because `created_at` isn't in the index. MySQL fetches potentially thousands of rows, then sorts them in a temporary buffer.

The fix is a **composite index** that covers the filter *and* the sort:

```php
// Migration
Schema::table('orders', function (Blueprint $table) {
    $table->index(['tenant_id', 'status', 'created_at'], 'orders_tenant_status_created_idx');
});

```

After adding this index, `EXPLAIN` shows `type: range`, `key: orders_tenant_status_created_idx`, and `Extra` no longer contains `Using filesort`. The optimizer can satisfy the entire query — filter and sort — by walking the index in order.

### When a Covering Index Goes Further

If your query only selects a handful of columns, you can make the index *covering* — MySQL never touches the table rows at all (`Extra: Using index`):

```php
$table->index(
    ['tenant_id', 'status', 'created_at', 'id', 'total_cents'],
    'orders_covering_idx'
);

```

Then in Eloquent:

```php
Order::select(['id', 'status', 'created_at', 'total_cents'])
    ->where('tenant_id', $tenantId)
    ->where('status', 'pending')
    ->orderBy('created_at')
    ->paginate(25);

```

`EXPLAIN` now shows `Extra: Using index`. Zero heap reads.

Automating Detection in CI
--------------------------

Add a Pest test that asserts no full-table scans on your critical queries:

```php
it('uses an index for the pending orders query', function () {
    $plan = DB::select(
        'EXPLAIN SELECT id, status, created_at FROM orders WHERE tenant_id = 1 AND status = "pending" ORDER BY created_at'
    );

    $types = collect($plan)->pluck('type');
    expect($types)->not->toContain('ALL');
});

```

This won't catch every regression, but it will catch the worst ones before they reach production.

Takeaways
---------

- `type: ALL` with a high `rows` estimate is your highest-priority fix.
- Composite indexes must match the column order: equality filters first, then range/sort columns.
- `EXPLAIN ANALYZE` gives actual timing — use it on a replica.
- Covering indexes eliminate heap reads entirely; use `select()` to keep them narrow.
- A simple Pest assertion on `EXPLAIN` output can prevent index regressions in CI.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fmsaied.com%2Farticles%2Fmysql-explain-demystified-reading-query-plans-to-kill-slow-laravel-queries&text=MySQL+EXPLAIN+Demystified%3A+Reading+Query+Plans+to+Kill+Slow+Laravel+Queries) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fmsaied.com%2Farticles%2Fmysql-explain-demystified-reading-query-plans-to-kill-slow-laravel-queries) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  Does adding more indexes always improve query performance in Laravel?        No. Every index adds overhead to INSERT, UPDATE, and DELETE operations and consumes disk space. Add indexes only where EXPLAIN confirms they are needed, and prefer composite indexes that serve multiple query patterns over many single-column indexes. 

      Q02  What is the difference between EXPLAIN and EXPLAIN ANALYZE in MySQL?        EXPLAIN shows the optimizer's estimated plan without executing the query. EXPLAIN ANALYZE (MySQL 8.0.18+) actually executes the query and returns both estimated and actual row counts plus timing per step. Use EXPLAIN ANALYZE on a replica to avoid production impact. 

      Q03  How do I find slow queries in a Laravel production app before using EXPLAIN?        Enable MySQL's slow query log or use Laravel Telescope / Debugbar in staging to surface queries exceeding a threshold. Once you have the raw SQL, run EXPLAIN against it on a replica to diagnose the plan. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://msaied.com/articles) 

 [ ![Laravel Eloquent Global Scopes: Pitfalls, Testing, and Composing Them Safely](https://cdn.msaied.com/211/8b9b19e7ecbf690b182ffbe6bffc9530.png) laravel eloquent testing 

### Laravel Eloquent Global Scopes: Pitfalls, Testing, and Composing Them Safely

Global scopes are powerful but easy to misuse. Learn how to write, test, and safely compose Eloquent global sc...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 16 Jun 2026     1 min read  

  Read    

 ](https://msaied.com/articles/laravel-eloquent-global-scopes-pitfalls-testing-and-composing-them-safely) [ ![Eloquent Custom Relations: Polymorphic Pivots, HasManyThrough Tricks, and Raw Join Relations](https://cdn.msaied.com/210/b47272214946c6adcd02ddf74b7df816.png) laravel eloquent database 

### Eloquent Custom Relations: Polymorphic Pivots, HasManyThrough Tricks, and Raw Join Relations

Beyond belongsTo and hasMany lies a set of underused Eloquent relation techniques. This guide covers custom re...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 16 Jun 2026     3 min read  

  Read    

 ](https://msaied.com/articles/eloquent-custom-relations-polymorphic-pivots-hasmanythrough-tricks-and-raw-join-relations) [ ![New in Laravel 12: Features, Helpers, and Upgrade Notes](https://cdn.msaied.com/209/c713447686bc1eb0a921b4027e4e4df8.png) laravel php upgrade 

### New in Laravel 12: Features, Helpers, and Upgrade Notes

Laravel 12 ships with a refined starter kit system, per-request context propagation, and several quality-of-li...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 16 Jun 2026     3 min read  

  Read    

 ](https://msaied.com/articles/new-in-laravel-12-features-helpers-and-upgrade-notes) 

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

Explore

- [Home](https://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [Contact](https://msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
