Chapter 09 · Optimizer, EXPLAIN, Statistics, and Query Plan Engineering

Join Algorithms, Semijoin/Antijoin Transformations, Derived Tables, and Materialization

Recognize MySQL join and subquery strategies from observable plans: nested-loop/index lookup, hash join, semijoin and antijoin transformations, plus derived-table/CTE merging and materialization.

Beginner → Intermediate130–165 minjoin-transformation labMySQL Community Server 8.4.10 LTS · InnoDB · free local labhash/semijoin/materializationLast reviewed: August 2026

Learning outcomes

Two SQL statements can express the same business question while presenting different optimization opportunities. MySQL may reorder inner joins, use indexed nested-loop lookups, build a hash table for an equijoin, transform an EXISTS into a semijoin, transform eligible negated forms into an antijoin, or merge/materialize a derived table or common table expression (CTE). The skill is not memorizing flags—it is recognizing operators and validating their work.

01

Recognize indexed lookup/nested-loop and hash-join patterns in TREE/ANALYZE output and explain when each becomes plausible.

02

Explain semijoin and antijoin semantics for existence questions without confusing them with ordinary joins that multiply rows.

03

Recognize derived-table/CTE merging versus materialization and identify SQL features that can prevent merging.

04

Use equivalent query formulations and EXPLAIN evidence to learn transformations rather than forcing optimizer switches by folklore.

05

Diagnose an intentionally poor or incomplete query shape and repair it while preserving result semantics.

Nested lookups versus hash joins

When an outer iterator produces a row and MySQL probes an index on the inner table, you can think of the execution as a nested sequence of lookups. When an equijoin has no useful join index, MySQL 8.4 can use a hash join: build a hash structure from one input and probe it with the other. Neither algorithm is universally superior; the optimizer chooses according to available access paths and costs.

Create two small disposable tables whose join key is intentionally unindexed:

sql · create a hash-join demonstration
USE servicehub_plan_lab;DROP TABLE IF EXISTS hash_left,hash_right;CREATE TABLE hash_left (id INT PRIMARY KEY, k INT NOT NULL, payload CHAR(20)) ENGINE=InnoDB;CREATE TABLE hash_right (id INT PRIMARY KEY, k INT NOT NULL, payload CHAR(20)) ENGINE=InnoDB;SET SESSION cte_max_recursion_depth=10000;INSERT INTO hash_leftWITH RECURSIVE s AS (SELECT 1 n UNION ALL SELECT n+1 FROM s WHERE n<5000)SELECT n,MOD(n,800),RPAD('L',20,'L') FROM s;INSERT INTO hash_rightWITH RECURSIVE s AS (SELECT 1 n UNION ALL SELECT n+1 FROM s WHERE n<4000)SELECT n,MOD(n,800),RPAD('R',20,'R') FROM s;ANALYZE TABLE hash_left,hash_right;EXPLAIN FORMAT=TREESELECT COUNT(*) FROM hash_left AS l JOIN hash_right AS r ON r.k=l.k;EXPLAIN ANALYZESELECT COUNT(*) FROM hash_left AS l JOIN hash_right AS r ON r.k=l.k;

Expected plan pattern on MySQL 8.4 when hash join is applicable: TREE output contains an Inner hash join plus a Hash child. If your build selects a different legal plan, record it rather than rewriting the lesson result. Now add a real join index and observe the candidate set change.

sql · make indexed lookup possible
CREATE INDEX ix_hash_right_k ON hash_right(k);ANALYZE TABLE hash_right;EXPLAIN FORMAT=TREESELECT COUNT(*) FROM hash_left AS l JOIN hash_right AS r ON r.k=l.k;EXPLAIN ANALYZESELECT COUNT(*) FROM hash_left AS l JOIN hash_right AS r ON r.k=l.k;

The new index does not guarantee a nested lookup for every dataset, but it gives the optimizer another strategy to cost. Compare actual rows, loops, and timing instead of declaring one algorithm “modern” and therefore always faster.

Semijoin: “does a match exist?”

An ordinary inner join returns an outer row once for every matching inner row. An existence question usually wants at most one copy of the outer row. MySQL can transform eligible IN or EXISTS subqueries into semijoin strategies such as table pullout, first match, loose scan, duplicate weedout, or materialization.

sql · express existence without duplicate multiplication
EXPLAIN FORMAT=TREESELECT c.customer_id,c.segmentFROM customers AS cWHERE EXISTS (  SELECT 1  FROM work_orders AS w  WHERE w.customer_id=c.customer_id    AND w.status='open');EXPLAIN ANALYZESELECT c.customer_id,c.segmentFROM customers AS cWHERE EXISTS (  SELECT 1 FROM work_orders AS w  WHERE w.customer_id=c.customer_id AND w.status='open');

Do not insist that one specific semijoin strategy appear; strategy selection is cost-based. The semantic requirement is that each qualifying customer appears once because the question asks whether at least one matching open work order exists.

Common wrong rewrite

Replacing EXISTS with an ordinary JOIN and then adding DISTINCT to remove duplicates can be correct for some queries, but it changes the optimizer problem and may create unnecessary row multiplication before duplicate removal. Start from the business semantics—existence—then inspect the plan.

Antijoin: “does no match exist?” and NULL awareness

NOT EXISTS naturally expresses customers with no matching row. Eligible negated subqueries can be transformed to antijoins. This is usually easier to reason about than NOT IN when NULL can appear in the subquery result, because SQL three-valued logic can make NOT IN unexpectedly unknown.

sql · find customers with no open work order
EXPLAIN FORMAT=TREESELECT c.customer_idFROM customers AS cWHERE NOT EXISTS (  SELECT 1 FROM work_orders AS w  WHERE w.customer_id=c.customer_id AND w.status='open');EXPLAIN ANALYZESELECT c.customer_idFROM customers AS cWHERE NOT EXISTS (  SELECT 1 FROM work_orders AS w  WHERE w.customer_id=c.customer_id AND w.status='open');

Look for antijoin-oriented TREE operators or a semantically equivalent strategy. The goal is to connect SQL meaning to optimizer transformation, not to memorize one printed phrase.

Derived tables: merge when possible, materialize when needed

A simple derived table may be merged into the outer query, effectively removing a materialization boundary. Aggregation, DISTINCT, grouping, window functions, and other constructs can prevent merging and require a temporary result.

sql · compare a mergeable and a materializing derived table
-- Simple derived table: often mergeable.EXPLAIN FORMAT=TREESELECT d.work_order_id,d.customer_idFROM (  SELECT work_order_id,customer_id,tenant_id,status  FROM work_orders) AS dWHERE d.tenant_id=17 AND d.status='open';-- Aggregation creates a meaningful boundary: materialization is plausible/required.EXPLAIN FORMAT=TREESELECT c.customer_id,c.segment,x.open_countFROM customers AS cJOIN (  SELECT customer_id,COUNT(*) AS open_count  FROM work_orders  WHERE status='open'  GROUP BY customer_id) AS x ON x.customer_id=c.customer_idWHERE c.tenant_id=17;EXPLAIN ANALYZESELECT c.customer_id,c.segment,x.open_countFROM customers AS cJOIN (  SELECT customer_id,COUNT(*) AS open_count  FROM work_orders  WHERE status='open'  GROUP BY customer_id) AS x ON x.customer_id=c.customer_idWHERE c.tenant_id=17;

When materialized, MySQL creates an internal temporary result for the derived table and may add an index to that temporary structure when beneficial. Do not assume “temporary table” means a coding mistake; sometimes materialization is exactly the cheapest legal execution strategy.

CTEs follow similar merge/materialize reasoning

Nonrecursive CTEs can also be merged or materialized depending on query structure and optimizer decisions. A CTE referenced multiple times may be materialized once and reused, depending on the plan. Recursive CTEs are a different mechanism and are not treated as ordinary merge candidates.

sql · inspect a CTE plan without forcing it
EXPLAIN FORMAT=TREEWITH recent AS (  SELECT work_order_id,customer_id,tenant_id,status,opened_at  FROM work_orders  WHERE opened_at >= '2026-06-01')SELECT c.segment,COUNT(*)FROM recent AS rJOIN customers AS c ON c.customer_id=r.customer_idWHERE r.tenant_id=17 AND r.status='open'GROUP BY c.segment;

If the CTE disappears into the outer plan, that is evidence of merging. If a materialization operator appears, explain which SQL feature or reuse pattern makes a separate result reasonable.

When a hint is useful: controlled experiment only

Optimizer hints such as NO_MERGE, SEMIJOIN, NO_SEMIJOIN, or join-order hints can isolate a hypothesis. They should not be the first teaching tool because enabling an optimization only permits it when applicable; it does not necessarily force one physical operator, and hint behavior can evolve.

Repair workflow

First capture the unhinted plan. Then change one variable at a time—index, statistics, query formulation, or a narrowly scoped hint—and compare actual work. Restore the default state after the experiment. If the root problem is missing indexing or bad estimates, fix that rather than shipping a permanent hint.

Verification and cleanup

sql · clean only the disposable join-algorithm tables
DROP TABLE IF EXISTS hash_left,hash_right;SHOW INDEX FROM work_orders;SELECT VERSION() AS server_version;

Knowledge check

  1. When is a hash join plausible in MySQL 8.4?
  2. What semantic question does a semijoin answer?
  3. Why is NOT EXISTS often easier to reason about than NOT IN?
  4. What are the two broad strategies for an eligible derived table or nonrecursive CTE?
  5. Should a materialized derived table automatically be considered bad?
Reveal answers
  1. For applicable equijoins, especially when no useful index can be applied to the join condition; the optimizer chooses it by cost.
  2. Whether at least one matching inner row exists, without multiplying the outer row by the number of matches.
  3. NOT IN is sensitive to NULL in the subquery result under three-valued logic; NOT EXISTS expresses absence directly.
  4. Merge it into the outer query block or materialize it as an internal temporary result.
  5. No. Materialization can be required by SQL semantics or be the optimizer’s cheapest strategy, and MySQL can optimize the temporary result further.

Summary and next step

You can now recognize several optimizer strategies without reducing tuning to flags. Lesson 5 combines everything into a plan-regression workflow using baselines, invisible indexes, statistics checks, and carefully bounded hints.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only assets compatible with the Ethereum/ERC-20 network. Do not send TRC-20/TRON assets.