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 / Install MariaDB on Windows and Start the Server

MariaDB Tutorial / Install MariaDB on Windows and Start the Server

Set up a local MariaDB server on Windows, verify its service and TCP connection, and create a database-scoped learning account. Follow practical checks for installation, port, path, and authentication problems.

MariaDB Tutorial / Select Columns, Aliases, Calculations, and DISTINCT

MariaDB Tutorial / Select Columns, Aliases, Calculations, and DISTINCT

Read explicit columns, label results with AS, calculate per-product stock values and understand how DISTINCT treats complete row combinations. Compare expected values and troubleshoot aliases and filters.

MariaDB Tutorial / Back Up an InnoDB Database with mariadb-dump

MariaDB Tutorial / Back Up an InnoDB Database with mariadb-dump

Create a scoped mariadb-dump backup with explicit consistency and file options. Check errors and hashes, inventory additional objects and define a separate-database restoration baseline.

MariaDB Tutorial / Use Subqueries, EXISTS, and NOT EXISTS

MariaDB Tutorial / Use Subqueries, EXISTS, and NOT EXISTS

Choose scalar and existence subqueries according to the result needed. Compare paid-order customers and absent-order customers, inspect correlation keys and avoid NULL-related exclusion assumptions.

MariaDB Tutorial / What MariaDB Is: Databases, SQL, and Its Relationship to MySQL

MariaDB Tutorial / What MariaDB Is: Databases, SQL, and Its Relationship to MySQL

Understand the roles of the MariaDB server and client, the structure of relational data, and the purpose of SQL. Learn how MariaDB relates to MySQL and prepare for a small product-database exercise.

MariaDB Tutorial / Break Queries into Named Steps with WITH and CTEs

MariaDB Tutorial / Break Queries into Named Steps with WITH and CTEs

Build a readable multi-step query with WITH, verify paid orders and per-order totals independently, and understand CTE scope, naming and execution-plan limitations.

MariaDB Tutorial / Calculate, Round, and Convert Numbers with ROUND and CAST

MariaDB Tutorial / Calculate, Round, and Convert Numbers with ROUND and CAST

Use exact DECIMAL inputs with ROUND and CAST, compare expected discount and midpoint results, and inspect conversion warnings, leading-zero loss and rounding-stage choices.

MariaDB Tutorial / Connect Python to MariaDB and Use Parameterized Queries

MariaDB Tutorial / Connect Python to MariaDB and Use Parameterized Queries

Use a complete PyMySQL program to connect, insert, select, commit, and roll back. Keep apostrophes, Unicode, and injection-shaped inputs as values, and restrict dynamic sorting through an identifier allowlist.

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 / Create a Database with CREATE DATABASE, USE, and utf8mb4

MariaDB Tutorial / Create a Database with CREATE DATABASE, USE, and utf8mb4

Create a practice database with explicit utf8mb4 settings and verify its definition. Understand connection-local database selection, collation comparisons, and why IF NOT EXISTS preserves existing defaults.