Summary

Query scans 2 tables and joins via block nested-loopLearn →; returns 150 rows in 16 ms. Main finding: Block Nested-LoopLearn → join — each batch of outer rows triggers a full scan of the inner table.

Total time
15.50 ms
Rows returned
150
Rows examined
250
Operators
3
Fix first

Primary recommendation

Add indexes on filter/join columns to avoid full table scans

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 t1.id, t1.name, t2.amount
from testdb.t1
  join testdb.t2

Execution plan (tree)

Click a plan operator to inspect details.

MySQL Query Plan Execution tree · Total: 15.50 ms · Click row to pin · Ctrl+F to search Expand All Collapse All SELF TOTAL % SELF TIME BAR Search Nested loop inner join 2.50 ms 15.50 ms 16% · Table scan [t1] 5.00 ms 32% · Table scan [t2] 8.00 ms 52% Click a row to pin details · Click ▾/▸ to expand/collapse · Ctrl+F to search

Warnings (2)

  1. WARN

    Full table scan: t1 (100 rows), t2 (150 rows)

    Labeled: Table scan [t1] Table scan [t2]

  2. WARN

    Block Nested-LoopLearn → (BNLLearn →) join buffer detected — uses join_buffer_size (Extra: 'Using join buffer (Block Nested Loop)Learn →', type ALL/index/range)

    Labeled: Table scan [t2]

Suggestions (2)

  1. HIGH

    Add indexes on filter/join columns to avoid full table scans

  2. MEDIUM

    Add indexes to eliminate BNLLearn → full/index scans, or increase join_buffer_size. In MySQL 8.0.20+, set block_nested_loopLearn →=off to force hash joinLearn → instead

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:

Open the full catalog →

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:23Z",
  "myflames_version": "1.5.0",
  "source": {
    "type": "file"
  },
  "plan_summary": {
    "total_time_ms": 15.5,
    "rows_sent": 150,
    "rows_examined_estimate": 250,
    "operator_count": 3,
    "max_depth": 2
  },
  "optimizer_switches": [
    {
      "name": "block_nested_loop",
      "value": "on",
      "explanation": "Buffers a batch of outer rows in a join buffer and scans the inner table once per batch instead of once per row. Inner still does a full/index/range scan — a missing index on the join column is the root cause.",
      "node_labels": [
        "Table scan [t2]"
      ]
    }
  ],
  "warnings": [
    {
      "severity": "warn",
      "category": "full_scan",
      "text": "Full table scan: t1 (100 rows), t2 (150 rows)",
      "source": "plan",
      "node_labels": [
        "Table scan [t1]",
        "Table scan [t2]"
      ]
    },
    {
      "severity": "warn",
      "category": "bnl",
      "text": "Block Nested-Loop (BNL) join buffer detected — uses join_buffer_size (Extra: 'Using join buffer (Block Nested Loop)', type ALL/index/range)",
      "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"
    },
    {
      "severity": "medium",
      "category": "tuning_variable",
      "action": "Add indexes to eliminate BNL full/index scans, or increase join_buffer_size. In MySQL 8.0.20+, set block_nested_loop=off to force hash join instead",
      "source": "plan",
      "target_variable": "join_buffer_size"
    }
  ],
  "executive_summary": "Query scans 2 tables and joins via block nested-loop; returns 150 rows in 16 ms. Main finding: Block Nested-Loop join — each batch of outer rows triggers a full scan of the inner table.",
  "plan_tree": {
    "node_id": "n:4ee53c8ca1ad",
    "short_label": "Nested loop inner join",
    "folded_label": "NESTED LOOP INNER starts=1 rows=150",
    "children": [
      {
        "node_id": "n:aa61800acadb",
        "short_label": "Table scan [t1]",
        "folded_label": "TABLE SCAN [t1] starts=1 rows=100",
        "children": []
      },
      {
        "node_id": "n:0ac48c48fb1d",
        "short_label": "Table scan [t2]",
        "folded_label": "TABLE SCAN [t2] starts=100 rows=150",
        "children": []
      }
    ]
  },
  "query": {
    "raw": "/* select#1 */ select t1.id, t1.name, t2.amount from testdb.t1 join testdb.t2",
    "beautified": "select t1.id, t1.name, t2.amount\nfrom testdb.t1\n  join testdb.t2"
  },
  "primary_action": {
    "ref": "suggestions[0]"
  },
  "teach_hooks": [
    {
      "lesson": "nested_loop",
      "match": {
        "folded_label": "NESTED LOOP INNER starts=1 rows=150",
        "short_label": "Nested loop inner join"
      },
      "controls": {
        "outer_rows": 100,
        "inner_rows": 15000,
        "row_size": 200
      },
      "note": "Nested loop inner join",
      "query_sql": "/* select#1 */ select t1.id, t1.name, t2.amount from testdb.t1 join testdb.t2"
    },
    {
      "lesson": "full_scan",
      "match": {
        "folded_label": "TABLE SCAN [t1] starts=1 rows=100",
        "short_label": "Table scan [t1]"
      },
      "controls": {
        "rows": 100,
        "row_size": 200,
        "selectivity": 100.0
      },
      "note": "Table scan on t1 (t1)",
      "query_sql": "/* select#1 */ select t1.id, t1.name, t2.amount from testdb.t1 join testdb.t2"
    },
    {
      "lesson": "bnl",
      "match": {
        "folded_label": "TABLE SCAN [t2] starts=100 rows=150",
        "short_label": "Table scan [t2]"
      },
      "controls": {
        "outer_rows": 15000,
        "inner_rows": 15000,
        "row_size": 200
      },
      "note": "Table scan on t2 (BNL join buffer) (t2)",
      "query_sql": "/* select#1 */ select t1.id, t1.name, t2.amount from testdb.t1 join testdb.t2"
    }
  ],
  "operator_complexities": [
    {
      "node_id": "n:4ee53c8ca1ad",
      "folded_label": "NESTED LOOP INNER starts=1 rows=150",
      "short_label": "Nested loop inner join",
      "complexity": {
        "big_o": "O(n · m)",
        "short": "n · m",
        "severity": "bad",
        "rationale": "Unindexed nested loop: every outer row drives a full scan of the inner side. This is the classic O(n²)-class blow-up.",
        "confidence": "exact",
        "learn_more": "nested_loop_join"
      }
    },
    {
      "node_id": "n:aa61800acadb",
      "folded_label": "TABLE SCAN [t1] starts=1 rows=100",
      "short_label": "Table scan [t1]",
      "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:0ac48c48fb1d",
      "folded_label": "TABLE SCAN [t2] starts=100 rows=150",
      "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"
      }
    }
  ]
}

Teach: Operator deep dive