AD JB Toolbox 01 EN

MariaDB Tutorial / Read EXPLAIN and Compare Plans Before and After an Index

EXPLAIN displays the optimizer’s selected execution plan. Compare the same query and returned results before and after indexing, and read access methods, chosen keys and estimated row counts without confusing estimates with measured runtime.

Read plan fields in context

Start with which tables are accessed and which keys can narrow their input. An ALL access type is not automatically wrong, and Using filesort does not alone prove a disk-file sort. Consider data size, selection ratio and actual latency. Use Design Single and Composite Indexes for Real Queries to justify candidate keys and the logging conditions in Inspect Server Settings, SQL Modes, Time Zones, and Logs to locate queries that are actually slow.

Field Read it as Avoid assuming
table The table or alias at this step One query can have several steps
type An access method A small-table scan can be reasonable
possible_keys / key Candidates / selected key An existing key must be selected
rows Estimated rows read The exact returned row count
Extra Filtering, sorting and covering details One phrase determines overall quality

Check the MariaDB documentation for the syntax and conditions used here: explain.

Generate a reproducible thousand-row fixture

Connect to shopcourse and check that digits_40_lab and explain_40_lab do not exist. The complete setup combines three decimal digits to generate 1000 rows without reading or changing actual application orders. Customer IDs range from one to one hundred, with ten orders per customer.

USE shopcourse;
CREATE TABLE digits_40_lab(n TINYINT PRIMARY KEY) ENGINE=InnoDB;
INSERT INTO digits_40_lab VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
CREATE TABLE explain_40_lab(
 order_id INT PRIMARY KEY,customer_id INT NOT NULL,
 ordered_at DATETIME NOT NULL,amount DECIMAL(10,2) NOT NULL
) ENGINE=InnoDB;
INSERT INTO explain_40_lab
SELECT 1+a.n+10*b.n+100*d.n,
 MOD(a.n+10*b.n+100*d.n,100)+1,
 DATE_ADD('2026-01-01',INTERVAL (a.n+10*b.n+100*d.n) MINUTE),1000
FROM digits_40_lab a CROSS JOIN digits_40_lab b CROSS JOIN digits_40_lab d;

Verify SELECT COUNT(*) FROM explain_40_lab; returns 1000. This artificial distribution teaches plan interpretation; it does not represent production skew, table size or load.

Compare the identical EXPLAIN before and after indexing

EXPLAIN SELECT order_id,ordered_at FROM explain_40_lab
WHERE customer_id=47 AND ordered_at>='2026-01-01'
ORDER BY ordered_at,order_id;
CREATE INDEX idx_e40_customer_date
ON explain_40_lab(customer_id,ordered_at,order_id);
EXPLAIN SELECT order_id,ordered_at FROM explain_40_lab
WHERE customer_id=47 AND ordered_at>='2026-01-01'
ORDER BY ordered_at,order_id;

The first plan has no secondary customer-time key; the second has a new candidate. Inspect the actual key choice and estimated rows in your output. Version, optimizer statistics and data distribution can change detailed estimates, so do not memorize one plan’s numbers as a universal result.

One observed plan from executing this thousand-row fixture on MariaDB 12.3.3 is shown below. Your values need not match: data, statistics and version can change the selected plan.

Stage type key rows Extra
Before index ALL NULL 1000 Using where; Using filesort
After index range idx_e40_customer_date 10 Using where; Using index

In this observation, a full scan became customer/time range access and estimated rows fell from 1000 to 10. This is not a measured hundredfold speedup. Verify the same ten returned orders and inspect execution data separately.

Verify actual output independently of estimated rows

SELECT order_id,ordered_at FROM explain_40_lab
WHERE customer_id=47 AND ordered_at>='2026-01-01'
ORDER BY ordered_at,order_id;

The returned identifiers are ten values beginning at 47 and increasing by 100 through 947. The same SELECT should return identical rows in the same order with or without the new index. EXPLAIN rows estimates an access step’s work, not the final result count.

A plan does not guarantee a latency. A meaningful timing comparison controls the query, data, concurrent load and cache conditions and uses repeated observations. Do not publish a service-wide improvement percentage from a tiny synthetic timing difference.

Separate executing ANALYZE from statistics maintenance

MariaDB’s ANALYZE statement family executes a query to obtain execution information. Do not treat it as a harmless EXPLAIN equivalent in front of UPDATE or DELETE. If runtime analysis is needed, use a suitable separate environment and inspect the precise scope and privileges first.

ANALYZE TABLE is a different operation that updates table statistics. Stale statistics can distort estimates, but refreshing statistics also has version-dependent costs and locking considerations. Investigate query structure and distribution before forcing a preferred plan with FORCE INDEX.

For actual execution information, apply the following only to this learning SELECT. ANALYZE FORMAT=JSON executes the query; distinguish measured fields such as r_rows and r_loops from estimated rows. Runtime depends on the environment and is not a fixed expected answer.

ANALYZE FORMAT=JSON
SELECT order_id,ordered_at FROM explain_40_lab
WHERE customer_id=47 AND ordered_at>='2026-01-01'
ORDER BY ordered_at,order_id;

ANALYZE FORMAT=JSON execution fields

Keep reproducible evidence and remove only this fixture

Record the server version, definitions and indexes, data-generation method, comparison query and before/after plans. After checking retention needs, remove only the two practice objects below. They have no foreign-key relationship, and no existing application-order table belongs in this cleanup.

DROP TABLE shopcourse.explain_40_lab;
DROP TABLE shopcourse.digits_40_lab;
More in This Category
MariaDB Tutorial / Design Single and Composite Indexes for Real Queries

MariaDB Tutorial / Design Single and Composite Indexes for Real Queries

Inspect existing keys, create a customer–time composite index and review leading columns, covering access and foreign-key dependencies. Preserve query results while evaluating read, write and storage costs.

MariaDB Tutorial / Create a Product Table, Insert Data, and Query It

MariaDB Tutorial / Create a Product Table, Insert Data, and Query It

Build a complete product table, insert three records, and verify its definition and query results. Practice price and stock filters, understand rerun behavior, and reset only confirmed disposable tutorial data.

MariaDB Tutorial / Start InnoDB Transactions with COMMIT and ROLLBACK

MariaDB Tutorial / Start InnoDB Transactions with COMMIT and ROLLBACK

Run a complete two-row InnoDB transaction, compare rollback and commit, and verify affected counts and balances. Understand connection scope, error handling, locks and implicit-commit boundaries.

MariaDB Tutorial / Read Related Rows with INNER JOIN

MariaDB Tutorial / Read Related Rows with INNER JOIN

Join customer names to orders with explicit relationship keys, interpret repeated customers correctly and compare filtered INNER JOIN results with an unrestricted Cartesian product.

MariaDB Tutorial / Save Reusable Query Definitions with VIEW

MariaDB Tutorial / Save Reusable Query Definitions with VIEW

Create an INVOKER view for paid-order totals, inspect its definition and observe transaction changes. Review security, updateability, dependencies and the difference between a view and stored result data.

MariaDB Tutorial / Compare Isolation Levels, Locks, and Deadlocks with Two Connections

MariaDB Tutorial / Compare Isolation Levels, Locks, and Deadlocks with Two Connections

Follow explicit A/B steps to compare isolation, row-lock waits and deadlocks. Complete blocks cover resetting values, rereading, cleaning up failures and restoring each connection’s settings.

MariaDB Tutorial / Restore a Backup into a Separate Database and Verify It

MariaDB Tutorial / Restore a Backup into a Separate Database and Verify It

Review a SQL dump, restore it into a verified empty database and compare object definitions, rows and goods totals. Handle source-file errors, compatibility and partial-restoration states explicitly.

MariaDB Tutorial / Define Input Rules with NULL, DEFAULT, NOT NULL, and CHECK

MariaDB Tutorial / Define Input Rules with NULL, DEFAULT, NOT NULL, and CHECK

Distinguish missing values, empty strings, defaults, and required inputs in MariaDB. Run valid and deliberately invalid inserts, inspect CHECK enforcement, and restore settings without touching existing product data.

MariaDB Tutorial / Design Table Relationships with Keys and Foreign Keys

MariaDB Tutorial / Design Table Relationships with Keys and Foreign Keys

Build and inspect customer, product, order and item tables with foreign keys. Verify one-to-many links, reject missing parents and choose deliberate deletion and historical-price policies.

MariaDB Tutorial / Combine Query Results with UNION and UNION ALL

MariaDB Tutorial / Combine Query Results with UNION and UNION ALL

Stack compatible customer-query results, compare UNION with UNION ALL and observe how source labels change duplicate detection. Check column meaning, result types and ordering scope.