Summary
Query scans 1 table; examines ~2,001 rows to return 49 in 0.35 ms. Main finding: no index covers (y, x) on t2.
- Total time
- 352 µs
- Rows returned
- 49
- Rows examined
- 2.00K
- Operators
- 5
Primary recommendation
Add indexes on filter/join columns to avoid full table scans
Candidate DDL (copy & run)
CREATE INDEX idx_t2_y_x ON t2 (y, x);
Why does this help?
An index turns a full-table scan into a direct lookup — MySQL jumps straight to the matching rows instead of reading every row and discarding the ones that don't match. For joins, the benefit compounds: a nested loop over N outer rows that had to scan the inner table becomes N fast lookups.
SQL
select testdb.t1.id AS id,testdb.t1.a AS a,testdb.t1.b AS b,testdb.t1.c AS c,testdb.t1.d AS d
from testdb.t1 semi
join (testdb.t2)
where ((testdb.t1.id = testdb.t2.x) and (testdb.t2.y < 100))
Execution plan (flamegraph)
Click a plan operator to inspect details.
Warnings (1)
-
WARN
Full table scan: t2 (2000 rows)
Labeled:
Table scan [t2]
Suggestions (1)
-
HIGH
Add indexes on filter/join columns to avoid full table scans
myteach — interactive algorithm catalog
Every operator shown above has a hands-on lesson with a cost-model slider and an animated walk-through. The catalog is the central index — bookmark it once, revisit per query:
Lessons relevant to this plan
Raw sidecar (JSON)
{
"$schema": "https://myflames.dev/schemas/sidecar-v1.json",
"schema_version": "1.3",
"generated_at": "2026-04-25T12:51:27Z",
"myflames_version": "1.5.0",
"source": {
"type": "file"
},
"plan_summary": {
"total_time_ms": 0.352,
"rows_sent": 49,
"rows_examined_estimate": 2001,
"operator_count": 5,
"max_depth": 4
},
"optimizer_switches": [
{
"name": "semijoin",
"value": "on",
"explanation": "IN / EXISTS subqueries are rewritten into a semi-join with the outer query and then resolved via one of the semijoin strategies (firstmatch, loosescan, duplicateweedout, or materialization).",
"node_labels": [
"Remove duplicate t1 rows using temporary table (we..."
]
},
{
"name": "duplicateweedout",
"value": "on",
"explanation": "Semijoin strategy: runs the subquery as a normal inner join and then removes duplicate outer rows using a temporary table keyed on their row IDs.",
"node_labels": [
"Remove duplicate t1 rows using temporary table (we..."
]
}
],
"warnings": [
{
"severity": "warn",
"category": "full_scan",
"text": "Full table scan: t2 (2000 rows)",
"source": "plan",
"node_labels": [
"Table scan [t2]"
]
}
],
"suggestions": [
{
"severity": "high",
"category": "index",
"action": "Add indexes on filter/join columns to avoid full table scans",
"source": "plan"
}
],
"executive_summary": "Query scans 1 table; examines ~2,001 rows to return 49 in 0.35 ms. Main finding: no index covers (y, x) on t2.",
"plan_tree": {
"node_id": "n:0fc991f45412",
"short_label": "Remove duplicate t1 rows using temporary table (we...",
"folded_label": "Remove duplicate t1 rows using temporary table (weedout) starts=1 rows=49",
"children": [
{
"node_id": "n:e32c3e0bcf9b",
"short_label": "Nested loop inner join",
"folded_label": "NESTED LOOP INNER starts=1 rows=49",
"children": [
{
"node_id": "n:215e07533821",
"short_label": "Filter: (((t2.y < 100) and (t2.x is not null)))",
"folded_label": "FILTER (((t2.y < 100) and (t2.x is not null))) starts=1 rows=49",
"children": [
{
"node_id": "n:39a4125027ad",
"short_label": "Table scan [t2]",
"folded_label": "TABLE SCAN [t2] starts=1 rows=2000",
"children": []
}
]
},
{
"node_id": "n:5c9e8b01da7e",
"short_label": "Single-row lookup [t1.PRIMARY]",
"folded_label": "SINGLE ROW LOOKUP [t1.PRIMARY] starts=49 rows=1",
"children": []
}
]
}
]
},
"query": {
"raw": "/* select#1 */ select `testdb`.`t1`.`id` AS `id`,`testdb`.`t1`.`a` AS `a`,`testdb`.`t1`.`b` AS `b`,`testdb`.`t1`.`c` AS `c`,`testdb`.`t1`.`d` AS `d` from `testdb`.`t1` semi join (`testdb`.`t2`) where ((`testdb`.`t1`.`id` = `testdb`.`t2`.`x`) and (`testdb`.`t2`.`y` < 100))",
"beautified": "select testdb.t1.id AS id,testdb.t1.a AS a,testdb.t1.b AS b,testdb.t1.c AS c,testdb.t1.d AS d\nfrom testdb.t1 semi\n join (testdb.t2)\nwhere ((testdb.t1.id = testdb.t2.x) and (testdb.t2.y < 100))"
},
"index_suggestions": [
{
"table": "t2",
"columns": [
"y",
"x"
],
"ddl": "CREATE INDEX idx_t2_y_x ON t2 (y, x);",
"reason": "Full scan on t2 with filter on (y, x)"
}
],
"primary_action": {
"ref": "suggestions[0]"
},
"teach_hooks": [
{
"lesson": "semijoin_weedout",
"match": {
"folded_label": "Remove duplicate t1 rows using temporary table (weedout) starts=1 rows=49",
"short_label": "Remove duplicate t1 rows using temporary table (we..."
},
"controls": {
"outer_rows": 49,
"inner_matches": 5,
"row_size": 200
},
"note": "Remove duplicate t1 rows using temporary table (weedout)",
"query_sql": "/* select#1 */ select `testdb`.`t1`.`id` AS `id`,`testdb`.`t1`.`a` AS `a`,`testdb`.`t1`.`b` AS `b`,`testdb`.`t1`.`c` AS `c`,`testdb`.`t1`.`d` AS `d` from `testdb`.`t1` semi join (`testdb`.`t2`) where ((`testdb`.`t1`.`id` = `testdb`.`t2`.`x`) and (`testdb`.`t2`.`y` < 100))"
},
{
"lesson": "nested_loop",
"match": {
"folded_label": "NESTED LOOP INNER starts=1 rows=49",
"short_label": "Nested loop inner join"
},
"controls": {
"outer_rows": 49,
"inner_rows": 49,
"row_size": 200
},
"note": "Nested loop inner join",
"query_sql": "/* select#1 */ select `testdb`.`t1`.`id` AS `id`,`testdb`.`t1`.`a` AS `a`,`testdb`.`t1`.`b` AS `b`,`testdb`.`t1`.`c` AS `c`,`testdb`.`t1`.`d` AS `d` from `testdb`.`t1` semi join (`testdb`.`t2`) where ((`testdb`.`t1`.`id` = `testdb`.`t2`.`x`) and (`testdb`.`t2`.`y` < 100))"
},
{
"lesson": "filter",
"match": {
"folded_label": "FILTER (((t2.y < 100) and (t2.x is not null))) starts=1 rows=49",
"short_label": "Filter: (((t2.y < 100) and (t2.x is not null)))"
},
"controls": {
"input_rows": 49,
"selectivity": 7.4
},
"note": "Filter: ((t2.y < 100) and (t2.x is not null))",
"query_sql": "/* select#1 */ select `testdb`.`t1`.`id` AS `id`,`testdb`.`t1`.`a` AS `a`,`testdb`.`t1`.`b` AS `b`,`testdb`.`t1`.`c` AS `c`,`testdb`.`t1`.`d` AS `d` from `testdb`.`t1` semi join (`testdb`.`t2`) where ((`testdb`.`t1`.`id` = `testdb`.`t2`.`x`) and (`testdb`.`t2`.`y` < 100))"
},
{
"lesson": "full_scan",
"match": {
"folded_label": "TABLE SCAN [t2] starts=1 rows=2000",
"short_label": "Table scan [t2]"
},
"controls": {
"rows": 2000,
"row_size": 200,
"selectivity": 100.0
},
"note": "Table scan on t2 (t2)",
"query_sql": "/* select#1 */ select `testdb`.`t1`.`id` AS `id`,`testdb`.`t1`.`a` AS `a`,`testdb`.`t1`.`b` AS `b`,`testdb`.`t1`.`c` AS `c`,`testdb`.`t1`.`d` AS `d` from `testdb`.`t1` semi join (`testdb`.`t2`) where ((`testdb`.`t1`.`id` = `testdb`.`t2`.`x`) and (`testdb`.`t2`.`y` < 100))"
},
{
"lesson": "unique_lookup",
"match": {
"folded_label": "SINGLE ROW LOOKUP [t1.PRIMARY] starts=49 rows=1",
"short_label": "Single-row lookup [t1.PRIMARY]"
},
"controls": {
"rows": 49,
"covering": false
},
"note": "Single-row index lookup on t1 using PRIMARY (id=t2.x) (t1)",
"query_sql": "/* select#1 */ select `testdb`.`t1`.`id` AS `id`,`testdb`.`t1`.`a` AS `a`,`testdb`.`t1`.`b` AS `b`,`testdb`.`t1`.`c` AS `c`,`testdb`.`t1`.`d` AS `d` from `testdb`.`t1` semi join (`testdb`.`t2`) where ((`testdb`.`t1`.`id` = `testdb`.`t2`.`x`) and (`testdb`.`t2`.`y` < 100))"
}
],
"operator_complexities": [
{
"node_id": "n:0fc991f45412",
"folded_label": "Remove duplicate t1 rows using temporary table (weedout) starts=1 rows=49",
"short_label": "Remove duplicate t1 rows using temporary table (we...",
"complexity": {
"big_o": "O(n log n)",
"short": "n log n",
"severity": "medium",
"rationale": "DuplicateWeedout semijoin: runs as a plain inner join, then drops duplicates using a temp-table index on the outer row IDs.",
"confidence": "typical",
"learn_more": "duplicate_weedout"
}
},
{
"node_id": "n:e32c3e0bcf9b",
"folded_label": "NESTED LOOP INNER starts=1 rows=49",
"short_label": "Nested loop inner join",
"complexity": {
"big_o": "O(n · log m)",
"short": "n · log m",
"severity": "medium",
"rationale": "Indexed nested loop: each outer row probes the inner table via an index descent.",
"confidence": "exact",
"learn_more": "nested_loop_join"
}
},
{
"node_id": "n:39a4125027ad",
"folded_label": "TABLE SCAN [t2] starts=1 rows=2000",
"short_label": "Table scan [t2]",
"complexity": {
"big_o": "O(n)",
"short": "n",
"severity": "medium",
"rationale": "Full table scan: the storage engine returns every row; cost scales with the table size.",
"confidence": "exact",
"learn_more": "full_table_scan"
}
},
{
"node_id": "n:5c9e8b01da7e",
"folded_label": "SINGLE ROW LOOKUP [t1.PRIMARY] starts=49 rows=1",
"short_label": "Single-row lookup [t1.PRIMARY]",
"complexity": {
"big_o": "O(log n)",
"short": "log n",
"severity": "good",
"rationale": "Unique-key lookup (eq_ref): one B+tree descent into the inner table per outer row — as fast as MySQL can get.",
"confidence": "exact",
"learn_more": "single_row_lookup"
}
}
]
}