EXPLAIN & query optimisation
Read EXPLAIN and EXPLAIN ANALYZE, spot full scans and filesorts, and rewrite slow queries.
When a query is slow, don't guess: ask MySQL how it runs the query. EXPLAIN shows the execution plan (which indexes, which join order, how many rows it expects to read), and EXPLAIN ANALYZE shows what actually happened. This lesson teaches you to read plans and fix the most common problems.
Setup#
Note that we deliberately didn't declare a foreign key, so orders.customer_id has no index yet.
Reading EXPLAIN#
Each row of the output is one table access, in the order MySQL performs them. The key columns are:
Here orders is read with type: ALL, scanning roughly 200,000 rows to find 10 orders. customers uses const through the primary key, which is fine. Multiply rows down the steps for a rough idea of the work. Fix the scan:
Now orders uses ref on idx_orders_customer with about 10 rows. Always index foreign-key columns (InnoDB does it automatically when you declare the foreign key).
Tree format and EXPLAIN ANALYZE#
EXPLAIN FORMAT=TREE shows the plan as nested operations, which is often easier to follow for joins. EXPLAIN ANALYZE (MySQL 8.0.18+) runs the query and adds the real numbers:
Read it from the innermost (most indented) line outwards. For each step, compare the estimated rows with the actual rows. Big differences mean the optimiser's statistics are off (ANALYZE TABLE refreshes them). actual time=first..last is in milliseconds, and loops tells you how many times a step ran. A step with many loops inside a join is a classic hotspot. Your timings will differ from these.
Remember that EXPLAIN ANALYZE really executes the query, so be careful with expensive statements on production servers.
Fixing filesort with the right index#
Using filesort: MySQL fetches all of the customer's orders and sorts them. An index that matches both the filter and the order lets it read rows already sorted and stop after 5:
The filesort is gone. Backward index scan means MySQL simply reads the index from the end to satisfy DESC. (MySQL 8 also supports real descending indexes, e.g. (customer_id, created_at DESC).) The new index begins with customer_id, so it also makes idx_orders_customer redundant. Drop the older one.
Ranges: make filters sargable#
A condition is sargable (Search-ARGument-ABLE) when MySQL can use an index for it. Keep the indexed column bare on one side of the comparison:
A checklist for slow queries#
- Find them. Turn on the slow query log (
slow_query_log = ON,long_query_time = 1) and summarise it withmysqldumpsloworpt-query-digest. Thesysschema viewsys.statements_with_runtimes_in_95th_percentilehelps too. - EXPLAIN them. Look for
ALLon big tables, largerowsestimates,Using filesortandUsing temporaryon large row counts. - Index for the query: equality columns first, then range or sort columns. Make the index covering if the query is very hot.
- Read less data: select only the columns you need, add
LIMIT, and use keyset pagination instead of hugeOFFSETs. - Rewrite when needed:
- Turn
ORacross different columns into aUNION ALLof two indexed queries. - Replace a per-row correlated subquery with a join or a window function.
- Aggregate in a derived table or CTE before joining, so you don't join millions of rows only to group them.
- Use
EXISTSrather thanCOUNT(*) > 0.
- Turn
- Fix the application: the "N+1 queries" problem (one query per item in a loop) is often worse than any single slow query. Fetch in batches with
IN (...)or a join. - Re-measure with
EXPLAIN ANALYZEor real timings. Undo changes that didn't help, because every index slows down writes.
Optimiser statistics and hints#
MySQL picks plans from statistics about your data. After big data changes, run ANALYZE TABLE t;. For skewed columns, MySQL 8 can store histograms, which help with columns that aren't indexed:
As a last resort, you can steer the optimiser with index hints (FORCE INDEX (idx), USE INDEX, IGNORE INDEX) or optimiser hints (SELECT /*+ JOIN_ORDER(o, c) */ ...). Hints freeze today's assumptions into your code, so prefer fixing indexes and statistics first.
What's next#
Fast queries are only half of production readiness. They must also be correct when many users change data at the same time. Next: transactions, ACID and isolation levels.
Check your understanding
Quick quiz
1.In EXPLAIN output, which
typevalue is the worst for a large table?2.What does
Using filesortin the Extra column tell you?3.What is the main difference between EXPLAIN and EXPLAIN ANALYZE?
Finished reading?
Mark this lesson complete to track your progress.