Mahamudul Hasan Rubel
HomeAboutProjectsSkillsExperienceBlogPhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • About
  • Projects
  • Skills
  • Experience
  • Blog
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
DatabasesJune 20, 20264 min read

Reading an EXPLAIN plan without panic: A Backend Engineer’s Guide

Reading an EXPLAIN plan is the most reliable way to optimize slow queries. Learn how to identify full table scans and index misses to save your production.

MySQLDatabasePerformanceBackendOptimizationSQLPostgreSQLRedis
Two people discussing an architectural floor plan with red markings on paper.

The last time I stared at a dashboard showing a 4,000ms latency spike, my first instinct was to start adding random indexes to the users table. That’s a trap, and it usually makes things worse. Instead, I pulled up the slow query log, grabbed the offending statement, and ran it through EXPLAIN.

If you’ve ever felt your heart rate climb when a database query hangs, you aren’t alone. But once you stop seeing EXPLAIN as a wall of cryptic text and start seeing it as a roadmap, the panic fades.

Understanding the EXPLAIN output

When you run EXPLAIN SELECT * FROM orders WHERE user_id = 42;, MySQL doesn't execute the query. Instead, it tells you how it intends to execute it. Most engineers get stuck because they try to parse every single column at once. You only need to care about a handful of them to solve 90% of your performance issues.

Here is the mental model I use to triage:

  • type: This is the most important column. It tells you how MySQL finds the rows.
  • key: The actual index being used. If this is NULL, you’re likely doing a full table scan.
  • rows: An estimate of how many rows MySQL has to examine.
  • Extra: Where the "gotchas" live, like Using filesort or Using temporary.

Why type is your best friend

The type column is essentially a grade for your query’s efficiency. You want to see const, eq_ref, or ref. If you see ALL, you're in trouble.

An ALL type means a full table scan. In a table with 50,000 rows, that’s manageable. In a table with 5 million rows, that’s a site-wide outage. I once spent about two days tracking down a slow report generation that was performing a full table scan on a logs table because the created_at column wasn't indexed. By adding a simple composite index, the query time dropped from 3 seconds to roughly 15ms.

Avoiding common pitfalls

Text cubes spelling 'DON'T' on a clean white background, ideal for concepts of caution or prohibition.

We’ve all been there: you add an index, but the query is still slow. I once tried to optimize a search feature by indexing a text column directly. It failed because MySQL can’t perform efficient range scans on large text fields. We had to pivot to a full-text index instead.

When reading an EXPLAIN plan, always check the Extra column for these warnings:

  1. Using filesort: MySQL has to do an extra pass to figure out how to order the data. This usually happens when your ORDER BY clause doesn't match your index.
  2. Using temporary: The database is creating a hidden temporary table to hold your results. This is a massive resource killer.
  3. Using index condition: This is actually a good sign! It means the database is using the index to filter rows before hitting the actual table data.

If you are dealing with complex queries, you might want to look into WordPress Database Optimization: Implementing HyperDB for Scaling to see how read-write splitting can change how you approach these plans. Sometimes the query isn't the problem; it's the architecture.

A quick workflow for your next incident

Don't guess. Follow this loop:

  1. Capture: Grab the exact query from the slow query log.
  2. Explain: Run EXPLAIN ANALYZE SELECT ... (if you're on MySQL 8.0+). The ANALYZE keyword provides actual execution timings rather than just estimates.
  3. Filter: Look at the type column first. If it's ALL, check your WHERE clause.
  4. Index: Add the index, then run EXPLAIN again to verify the key column has updated.

If you find that your application layer is making too many of these queries in a loop, you might have an N+1 problem. While Designing a clean service layer in Laravel without over-abstraction focuses on code structure, keeping your service layer lean often prevents these database-heavy loops from ever being written in the first place.

Frequently Asked Questions

Close-up of a magnifying glass focusing on the phrase 'Frequently Asked Questions'.

Does EXPLAIN work the same on every database? Not exactly. PostgreSQL and MySQL have different output formats, but the core concepts (index usage, scan types, and cost estimates) remain the same.

What if my query uses an index but is still slow? Check the rows column. If MySQL is reading 100,000 rows to return 1, you have a poorly selective index. You might need a composite index that covers more of your filtering criteria.

Should I always aim for the fastest possible query? No. Premature optimization is the root of all evil. If a query runs in 50ms and the page load is already under 200ms, don't waste time trying to get it to 5ms. Spend that time on features or technical debt that actually matters.

I still get nervous when I see a query hitting a massive table, but I don't panic anymore. I just run the plan, look for the ALL type, and check my indexes. It's a boring, repetitive process, but it works every time. Next time, I might try using optimizer_trace to see exactly why the engine chose a specific path, but for now, sticking to the basics has saved me more production incidents than I care to count.

Back to Blog

Similar Posts

Multiple stacks of blue medical face masks arranged on a white background, emphasizing cleanliness and healthcare.
DatabasesJune 20, 20264 min read

Redis Caching Patterns That Prevent Stampedes in Production

Redis caching patterns that prevent stampedes are essential for scaling. Learn how to stop the thundering herd effect and keep your backend performance stable.

Read more
Scrabble tiles spelling 'DATA' on a wooden table with a blurred plant background.
Databases
June 20, 2026
4 min read

Killing N+1 queries at the database layer: A practical guide

Killing N+1 queries at the database layer is the fastest way to stabilize your app. Learn how to identify, debug, and eliminate performance-killing bottlenecks.

Read more
Close-up of a smartphone showing Python code on the display, showcasing coding and technology.
DatabasesJune 20, 20264 min read

Indexing Strategy for App Developers: Stop Slow Queries

Master an indexing strategy for app developers to fix slow production queries. Learn how to read EXPLAIN plans, pick the right columns, and avoid overhead.

Read more