LearnContact
Lesson 7520 min read

EXPLAIN Statement

Learn how to use the EXPLAIN statement to see exactly how MySQL plans to execute a query, and how to spot slow full table scans.

Introduction

In the last two lessons, you learned principles and techniques for writing faster queries — but how do you actually know whether a given query is following those techniques, or whether an index is even being used at all? Guessing is not good enough. MySQL provides a tool that answers this directly: EXPLAIN.

EXPLAIN is one of the most useful commands you will ever run against a MySQL database. It turns "I think this query is slow" into "here is exactly why, and here is exactly what to fix."

What You Will Learn
  • What the EXPLAIN statement actually does.
  • How to run EXPLAIN in front of any SELECT query.
  • How to read the key columns of its output: type, possible_keys, key, and rows.
  • What a full table scan looks like in the output, and why it matters.
  • A worked example comparing EXPLAIN output before and after adding an index.

What Does EXPLAIN Do?

EXPLAIN shows you the execution plan MySQL intends to use for a query — which indexes (if any) it will use, in what order it will access tables, and roughly how many rows it expects to examine. Placed in front of a SELECT, it reports this plan back to you instead of running the query for its actual result set.

This makes EXPLAIN completely safe to run on any query, including ones that would normally return a huge number of rows — you get the plan back almost instantly, without waiting for (or being affected by) the full query.

Note

EXPLAIN describes what MySQL's optimizer plans to do based on the table's statistics. It is extremely reliable, but on rare occasions the actual execution can differ slightly — it is a very strong guide, not an absolute guarantee.

Running EXPLAIN

Using EXPLAIN is as simple as adding the keyword in front of any SELECT statement.

EXPLAIN SELECT order_id, total
FROM orders
WHERE customer_id = 42;
Result
+----+-------------+--------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+--------+------+---------------+------+---------+------+------+-------------+
| 1  | SIMPLE      | orders | ALL  | NULL          | NULL | NULL    | NULL | 8000 | Using where |
+----+-------------+--------+------+---------------+------+---------+------+------+-------------+

This particular result shows a table with no useful index on customer_id yet — notice type is ALL and key is NULL. You will fix exactly this in the worked example further down.

Reading the Output

EXPLAIN's output has several columns, but four of them tell you almost everything you need for day-to-day tuning.

ColumnMeaning
typeThe access method MySQL will use. From worst to best: ALL (full scan), index, range, ref, eq_ref, const.
possible_keysIndexes MySQL considered using for this query. NULL means no index looked usable.
keyThe index MySQL actually decided to use. NULL means none was used.
rowsMySQL's estimate of how many rows it will have to examine to produce the result.
What to Look For

A healthy query typically shows a value other than ALL in type, a real index name (not NULL) in key, and a rows estimate close to the number of rows you actually expect to match — not close to the entire table.

Full Table Scans

When type shows ALL, MySQL is reading every single row in the table to find the ones that match. On a small lookup table with a handful of rows, that is completely fine and often faster than using an index at all. On a table with millions of rows, a full table scan on a frequently-run query is a serious performance red flag.

Red Flag

type: ALL combined with a large rows estimate on a query that runs often (like one triggered on every page load) usually means a missing index is costing you real performance — this is exactly the pattern to look for and fix.

Example: Before and After an Index

Here is the same query, run before and after adding an index on the column it filters by. Watch how type, key, and rows all change.

before_index.sql
EXPLAIN SELECT order_id, total
FROM orders
WHERE customer_id = 42;
Before: No Index
+----+-------------+--------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+--------+------+---------------+------+---------+------+------+-------------+
| 1  | SIMPLE      | orders | ALL  | NULL          | NULL | NULL    | NULL | 8000 | Using where |
+----+-------------+--------+------+---------------+------+---------+------+------+-------------+
add_index.sql
CREATE INDEX idx_orders_customer_id ON orders (customer_id);

EXPLAIN SELECT order_id, total
FROM orders
WHERE customer_id = 42;
After: Index Added
+----+-------------+--------+------+------------------------+------------------------+---------+-------+------+-------+
| id | select_type | table  | type | possible_keys          | key                    | key_len | ref   | rows | Extra |
+----+-------------+--------+------+------------------------+------------------------+---------+-------+------+-------+
| 1  | SIMPLE      | orders | ref  | idx_orders_customer_id | idx_orders_customer_id | 4       | const | 2    | NULL  |
+----+-------------+--------+------+------------------------+------------------------+---------+-------+------+-------+

Before the index, MySQL had no choice but to scan all 8,000 rows (type: ALL). After the index, type becomes ref, key shows the new index is actually being used, and the rows estimate drops from 8,000 to just 2 — MySQL now jumps almost directly to the matching rows.

Common Mistakes

Avoid These Mistakes
  • Assuming a query is fast without ever running EXPLAIN to check.
  • Seeing a value in possible_keys and assuming that index is actually being used — only the key column confirms that.
  • Ignoring type: ALL on a large, frequently-run table because the query "still finishes quickly" today.
  • Adding an index and never re-running EXPLAIN to confirm it actually changed the plan.

Key Takeaways

  • EXPLAIN shows how MySQL plans to execute a query without running it for its full result set.
  • The type, possible_keys, key, and rows columns tell you almost everything you need day-to-day.
  • type: ALL means a full table scan, which is a red flag on large, frequently-queried tables.
  • Adding the right index can change type from ALL to something like ref, and dramatically shrink the rows estimate.

Summary

EXPLAIN turns query tuning from guesswork into evidence. Instead of assuming a query is slow (or fast), you can see precisely which indexes MySQL is using and roughly how much work it expects to do.

In this lesson, you learned what EXPLAIN does, how to run it, how to read its key output columns, and how adding an index visibly improves the plan. Next, you will start connecting MySQL to real application code, beginning with PHP.

Lesson 75 Completed
  • You can run EXPLAIN in front of any SELECT query.
  • You can read the type, possible_keys, key, and rows columns.
  • You can recognize a full table scan and understand why it matters.
  • You are ready to connect MySQL to application code, starting with PHP.
Next Lesson →

MySQL with PHP