Lesson 0003 · Internals & Performance
The optimizer already wrote down its plan for every query — this lesson teaches you to read it in one fixed pass and know instantly whether it's good.
In 0001 and 0002
you predicted how MySQL would use an index. EXPLAIN is how you
check — for any query, on any table. It prints the optimizer's chosen
plan without running the statement. The trick is to stop reading it left-to-right and
instead read five columns in a fixed order of importance.
type → key → rows → filtered → Extra. type is the
headline (how it reaches rows), key is which index it actually used,
rows × filtered is how many rows it expects to carry forward,
and Extra is where the warnings hide. Read those five and you know the plan.
type — the access-method ladderThis is the single most important value. It tells you how MySQL reaches rows, and it has a strict best-to-worst order. Learn the ladder and you can grade a plan at a glance. — MySQL Manual — EXPLAIN Join Types
| type | Meaning | Ties back to |
|---|---|---|
| const | At most one matching row, read once. Unbeatable. | PK / unique = constant |
| eq_ref | One row per join combination via a PK / UNIQUE NOT NULL key. Best join type. | Clustered lookup (0001) |
| ref | All rows matching an index value — non-unique key or a leftmost prefix. Good if few rows. | Secondary / prefix (0002) |
| range | Rows in a range, via an index (>, BETWEEN, IN). | Range stops prefix (0002) |
| index | Full scan of the index tree (smaller than the table; often covering). | Covering (0001) |
| ALL | Full table scan — every row. Usually the thing to fix. | No usable prefix (0002) |
Rule of thumb: const/eq_ref/ref are usually fine;
range depends on how wide; index and especially ALL on a big
table are red flags unless the row count is tiny.
key — which index actually wonpossible_keys lists indexes MySQL could use; key is the
one it chose (NULL = none, i.e. a scan). A common bug signal:
possible_keys names your index but key is NULL — the
optimizer decided a scan was cheaper, often because the filter isn't a leftmost prefix or the
column is wrapped in a function.
— MySQL Manual: "the key column indicates the key (index) that MySQL actually decided to use."
rows × filtered — the work estimaterows is how many rows MySQL estimates it must examine (for InnoDB,
an estimate, not exact). filtered is the percentage expected to survive the
WHERE. Their product is the rows carried into the next join step.
— MySQL Manual: "rows × filtered shows the number of rows that are joined with the following table."
rows: 1000, filtered: 50.00 → about 500 rows
flow forward. Big rows with tiny filtered means MySQL is reading
a lot to keep a little — a sign an index could do the filtering earlier.
Extra — where the warnings liveSmall text, big meaning. The four you'll see constantly:
| Extra | What it tells you |
|---|---|
Using index | Covering — answered from the index alone, no clustered lookup (0001). |
Using where | Rows are filtered after the storage engine returns them. |
Using index condition | Index Condition Pushdown — part of the WHERE checked in the index first. |
Using filesort | A separate sort was needed — the index didn't supply the order (0002). |
EXPLAIN ANALYZEPlain EXPLAIN shows estimates. When the estimate lies (stale
statistics, skewed data), run EXPLAIN ANALYZE — it actually
executes the query and prints actual time and rows next to the estimates, in a tree
of iterators. When estimated rows and actual rows diverge wildly,
you've found why the optimizer chose badly.
— MySQL Manual: "EXPLAIN ANALYZE … runs a statement and produces EXPLAIN output along with timing and … information about how the optimizer's expectations matched the actual execution."
Answer from memory — effortful recall is what builds retention. Feedback is immediate.
The type column in EXPLAIN reports:
type is the access/join type — how MySQL reaches rows (const, eq_ref, ref, range, index, ALL). It's the headline value for grading a plan.A PK or UNIQUE-NOT-NULL equality join shows access type:
eq_ref reads one row per combination via a PK / UNIQUE-NOT-NULL index — the best join type after const. ref is for non-unique keys or prefixes.An access type of ALL means the query did:
ALL is a full table scan — every row examined. On a large table it's the usual thing to fix by adding a usable index.Multiplying rows by filtered estimates the:
rows × filtered is the estimated number of rows that survive the WHERE and are joined with the next table — the funnel's output.Extra: Using index is telling you there is:
Using index means the query was answered from the index alone (covering), skipping the clustered-index lookup from Lesson 0001. Don't confuse it with Using where.EXPLAIN ANALYZE differs from plain EXPLAIN because it:
EXPLAIN ANALYZE executes the statement and reports actual time and rows beside the estimates, exposing where the optimizer's guesses were wrong.Reuse the people table from Lesson 0002
(index name (last_name, first_name), PK id). Read the five columns each time.
-- A) PK equality → expect type: const
EXPLAIN SELECT * FROM people WHERE id = 1;
-- B) prefix equality → expect type: ref, key: name
EXPLAIN SELECT * FROM people WHERE last_name = 'Jones';
-- C) covering → expect Extra: Using index
EXPLAIN SELECT last_name, first_name FROM people WHERE last_name = 'Jones';
-- D) non-prefix filter → expect type: ALL, key: NULL
EXPLAIN SELECT * FROM people WHERE first_name = 'John';
-- E) estimates vs reality — actual rows beside estimated
EXPLAIN ANALYZE SELECT * FROM people WHERE last_name = 'Jones';
A const (top of the ladder). B
type: ref, key: name. C adds
Extra: Using index — covering. D falls to
type: ALL, key: NULL — the leftmost-prefix rule biting.
E prints actual time … rows=N next to the estimate. On this
tiny table estimates match; on a real table, that gap is your diagnostic. Bring me any plan
where key was NULL but you expected an index.
MySQL 8.0 Reference Manual — EXPLAIN Output Format.
The definitive column-by-column reference, including every type and every
Extra value. Pair it with
EXPLAIN ANALYZE for the
execution-time view.