Summary

Query scans 2 tables, materializes 1 temp tableLearn → and sorts the result; examines ~601 rows to return 10 in 1.6 ms. Main finding: full scan of org_chart (1,000 rows).

Total time
1.55 ms
Rows returned
10
Rows examined
601
Operators
10
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

with recursive org_chart as (/*
select#2 */
select demodb.employees.id AS id,demodb.employees.name AS name,demodb.employees.manager_id AS manager_id,1 AS level
from demodb.employees
where (demodb.employees.manager_id is null)
union all /*
select#3 */
select e.id AS id,e.name AS name,e.manager_id AS manager_id,(oc.level + 1) AS oc.level + 1
from demodb.employees e
  join org_chart oc
where ((e.manager_id = oc.id) and (oc.level < 10))) /*
select#1 */
select org_chart.level AS level,count(0) AS headcount
from org_chart
group by org_chart.level
order by org_chart.level

Execution plan (tree)

Click a plan operator to inspect details.

MySQL Query Plan Execution tree · Total: 1.55 ms · Click row to pin · Ctrl+F to search Expand All Collapse All SELF TOTAL % SELF TIME BAR Search Sort 7.6 µs 1.55 ms 0.5% Table scan [<temporary>] 0.8 µs 1.54 ms 0.1% Aggregate 98.7 µs 1.54 ms 6% Table scan [org_chart] 67.4 µs 1.44 ms 4% Materialize 146.2 µs 1.38 ms 9% · Index lookup [employees.idx_mgr] 65.7 µs 4% Nested loop inner join 100.5 µs 1.16 ms 6% Filter: (((oc.level < 10) and (oc.id is not n… 54.0 µs 97.6 µs 3% · Scan new records on oc 43.6 µs 3% · Index lookup [employees.idx_mgr] 966.8 µs 62% Click a row to pin details · Click ▾/▸ to expand/collapse · Ctrl+F to search

Warnings (3)

  1. WARN

    Full table scan: <temporary> (10 rows), org_chart (1000 rows)

    Labeled: Table scan [<temporary>] Table scan [org_chart]

  2. WARN

    1 temp tableLearn →(s) (Materialize) — up to 1000 rows; may spill to disk

    Labeled: Materialize

  3. WARN

    1 sort operationLearn →(s) — 10 rows; may use disk-based filesortLearn →

    Labeled: Sort

Suggestions (3)

  1. HIGH

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

  2. MEDIUM

    Increase tmp_table_size / max_heap_table_size to keep temp tables in memory

  3. MEDIUM

    Increase sort_buffer_size or add an ordered index to avoid filesortLearn →

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:26Z",
  "myflames_version": "1.5.0",
  "source": {
    "type": "file"
  },
  "plan_summary": {
    "total_time_ms": 1.551,
    "rows_sent": 10,
    "rows_examined_estimate": 601,
    "operator_count": 10,
    "max_depth": 8
  },
  "optimizer_switches": [
    {
      "name": "index_condition_pushdown",
      "value": "on",
      "explanation": "Parts of the WHERE clause are evaluated inside the storage engine using index columns before fetching the full row — reduces rows handed to the server layer.",
      "node_labels": [
        "Index lookup [employees.idx_mgr]"
      ]
    },
    {
      "name": "materialization",
      "value": "on",
      "explanation": "A subquery or derived table is executed once and its result stored in a temporary table for reuse. Watch tmp_table_size / max_heap_table_size — it may spill to disk.",
      "node_labels": [
        "Materialize"
      ]
    }
  ],
  "warnings": [
    {
      "severity": "warn",
      "category": "full_scan",
      "text": "Full table scan: <temporary> (10 rows), org_chart (1000 rows)",
      "source": "plan",
      "node_labels": [
        "Table scan [<temporary>]",
        "Table scan [org_chart]"
      ]
    },
    {
      "severity": "warn",
      "category": "temp_table",
      "text": "1 temp table(s) (Materialize) — up to 1000 rows; may spill to disk",
      "source": "plan",
      "node_labels": [
        "Materialize"
      ]
    },
    {
      "severity": "warn",
      "category": "filesort",
      "text": "1 sort operation(s) — 10 rows; may use disk-based filesort",
      "source": "plan",
      "node_labels": [
        "Sort"
      ]
    }
  ],
  "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": "Increase tmp_table_size / max_heap_table_size to keep temp tables in memory",
      "source": "plan",
      "target_variable": "tmp_table_size"
    },
    {
      "severity": "medium",
      "category": "tuning_variable",
      "action": "Increase sort_buffer_size or add an ordered index to avoid filesort",
      "source": "plan",
      "target_variable": "sort_buffer_size"
    }
  ],
  "executive_summary": "Query scans 2 tables, materializes 1 temp table and sorts the result; examines ~601 rows to return 10 in 1.6 ms. Main finding: full scan of org_chart (1,000 rows).",
  "plan_tree": {
    "node_id": "n:347eeced0b3c",
    "short_label": "Sort",
    "folded_label": "SORT starts=1 rows=10",
    "children": [
      {
        "node_id": "n:ca439ce5f715",
        "short_label": "Table scan [<temporary>]",
        "folded_label": "TABLE SCAN [<temporary>] starts=1 rows=10",
        "children": [
          {
            "node_id": "n:604ff20c53db",
            "short_label": "Aggregate",
            "folded_label": "AGGREGATE starts=1 rows=10",
            "children": [
              {
                "node_id": "n:23956a72ce60",
                "short_label": "Table scan [org_chart]",
                "folded_label": "TABLE SCAN [org_chart] starts=1 rows=1000",
                "children": [
                  {
                    "node_id": "n:0d118da99553",
                    "short_label": "Materialize",
                    "folded_label": "MATERIALIZE starts=1 rows=1000",
                    "children": [
                      {
                        "node_id": "n:690d9483b0a5",
                        "short_label": "Index lookup [employees.idx_mgr]",
                        "folded_label": "INDEX LOOKUP [employees.idx_mgr] starts=1 rows=100",
                        "children": []
                      },
                      {
                        "node_id": "n:b76c70582649",
                        "short_label": "Nested loop inner join",
                        "folded_label": "NESTED LOOP INNER starts=2 rows=450",
                        "children": [
                          {
                            "node_id": "n:e0f25f0beb5a",
                            "short_label": "Filter: (((oc.level < 10) and (oc.id is not null)))",
                            "folded_label": "FILTER (((oc.level < 10) and (oc.id is not null))) starts=2 rows=450",
                            "children": [
                              {
                                "node_id": "n:621a4de55271",
                                "short_label": "Scan new records on oc",
                                "folded_label": "Scan new records on oc starts=2 rows=500",
                                "children": []
                              }
                            ]
                          },
                          {
                            "node_id": "n:911d971460cb",
                            "short_label": "Index lookup [employees.idx_mgr]",
                            "folded_label": "INDEX LOOKUP [employees.idx_mgr] starts=900 rows=1",
                            "children": []
                          }
                        ]
                      }
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  },
  "query": {
    "raw": "with recursive `org_chart` as (/* select#2 */ select `demodb`.`employees`.`id` AS `id`,`demodb`.`employees`.`name` AS `name`,`demodb`.`employees`.`manager_id` AS `manager_id`,1 AS `level` from `demodb`.`employees` where (`demodb`.`employees`.`manager_id` is null) union all /* select#3 */ select `e`.`id` AS `id`,`e`.`name` AS `name`,`e`.`manager_id` AS `manager_id`,(`oc`.`level` + 1) AS `oc.level + 1` from `demodb`.`employees` `e` join `org_chart` `oc` where ((`e`.`manager_id` = `oc`.`id`) and (`oc`.`level` < 10))) /* select#1 */ select `org_chart`.`level` AS `level`,count(0) AS `headcount` from `org_chart` group by `org_chart`.`level` order by `org_chart`.`level`",
    "beautified": "with recursive org_chart as (/*\nselect#2 */\nselect demodb.employees.id AS id,demodb.employees.name AS name,demodb.employees.manager_id AS manager_id,1 AS level\nfrom demodb.employees\nwhere (demodb.employees.manager_id is null)\nunion all /*\nselect#3 */\nselect e.id AS id,e.name AS name,e.manager_id AS manager_id,(oc.level + 1) AS oc.level + 1\nfrom demodb.employees e\n  join org_chart oc\nwhere ((e.manager_id = oc.id) and (oc.level < 10))) /*\nselect#1 */\nselect org_chart.level AS level,count(0) AS headcount\nfrom org_chart\ngroup by org_chart.level\norder by org_chart.level"
  },
  "primary_action": {
    "ref": "suggestions[0]"
  },
  "teach_hooks": [
    {
      "lesson": "filesort",
      "match": {
        "folded_label": "SORT starts=1 rows=10",
        "short_label": "Sort"
      },
      "controls": {
        "rows": 10,
        "row_size": 200
      },
      "note": "Sort: org_chart.`level`",
      "query_sql": "with recursive `org_chart` as (/* select#2 */ select `demodb`.`employees`.`id` AS `id`,`demodb`.`employees`.`name` AS `name`,`demodb`.`employees`.`manager_id` AS `manager_id`,1 AS `level` from `demodb`.`employees` where (`demodb`.`employees`.`manager_id` is null) union all /* select#3 */ select `e`.`id` AS `id`,`e`.`name` AS `name`,`e`.`manager_id` AS `manager_id`,(`oc`.`level` + 1) AS `oc.level + 1` from `demodb`.`employees` `e` join `org_chart` `oc` where ((`e`.`manager_id` = `oc`.`id`) and (`oc`.`level` < 10))) /* select#1 */ select `org_chart`.`level` AS `level`,count(0) AS `headcount` from `org_chart` group by `org_chart`.`level` order by `org_chart`.`level`"
    },
    {
      "lesson": "full_scan",
      "match": {
        "folded_label": "TABLE SCAN [<temporary>] starts=1 rows=10",
        "short_label": "Table scan [<temporary>]"
      },
      "controls": {
        "rows": 10,
        "row_size": 200,
        "selectivity": 100.0
      },
      "note": "Table scan on <temporary> (<temporary>)",
      "query_sql": "with recursive `org_chart` as (/* select#2 */ select `demodb`.`employees`.`id` AS `id`,`demodb`.`employees`.`name` AS `name`,`demodb`.`employees`.`manager_id` AS `manager_id`,1 AS `level` from `demodb`.`employees` where (`demodb`.`employees`.`manager_id` is null) union all /* select#3 */ select `e`.`id` AS `id`,`e`.`name` AS `name`,`e`.`manager_id` AS `manager_id`,(`oc`.`level` + 1) AS `oc.level + 1` from `demodb`.`employees` `e` join `org_chart` `oc` where ((`e`.`manager_id` = `oc`.`id`) and (`oc`.`level` < 10))) /* select#1 */ select `org_chart`.`level` AS `level`,count(0) AS `headcount` from `org_chart` group by `org_chart`.`level` order by `org_chart`.`level`"
    },
    {
      "lesson": "tmp",
      "match": {
        "folded_label": "AGGREGATE starts=1 rows=10",
        "short_label": "Aggregate"
      },
      "controls": {
        "rows": 10,
        "row_size": 200
      },
      "note": "Aggregate using temporary table",
      "query_sql": "with recursive `org_chart` as (/* select#2 */ select `demodb`.`employees`.`id` AS `id`,`demodb`.`employees`.`name` AS `name`,`demodb`.`employees`.`manager_id` AS `manager_id`,1 AS `level` from `demodb`.`employees` where (`demodb`.`employees`.`manager_id` is null) union all /* select#3 */ select `e`.`id` AS `id`,`e`.`name` AS `name`,`e`.`manager_id` AS `manager_id`,(`oc`.`level` + 1) AS `oc.level + 1` from `demodb`.`employees` `e` join `org_chart` `oc` where ((`e`.`manager_id` = `oc`.`id`) and (`oc`.`level` < 10))) /* select#1 */ select `org_chart`.`level` AS `level`,count(0) AS `headcount` from `org_chart` group by `org_chart`.`level` order by `org_chart`.`level`"
    },
    {
      "lesson": "full_scan",
      "match": {
        "folded_label": "TABLE SCAN [org_chart] starts=1 rows=1000",
        "short_label": "Table scan [org_chart]"
      },
      "controls": {
        "rows": 1000,
        "row_size": 200,
        "selectivity": 100.0
      },
      "note": "Table scan on org_chart (org_chart)",
      "query_sql": "with recursive `org_chart` as (/* select#2 */ select `demodb`.`employees`.`id` AS `id`,`demodb`.`employees`.`name` AS `name`,`demodb`.`employees`.`manager_id` AS `manager_id`,1 AS `level` from `demodb`.`employees` where (`demodb`.`employees`.`manager_id` is null) union all /* select#3 */ select `e`.`id` AS `id`,`e`.`name` AS `name`,`e`.`manager_id` AS `manager_id`,(`oc`.`level` + 1) AS `oc.level + 1` from `demodb`.`employees` `e` join `org_chart` `oc` where ((`e`.`manager_id` = `oc`.`id`) and (`oc`.`level` < 10))) /* select#1 */ select `org_chart`.`level` AS `level`,count(0) AS `headcount` from `org_chart` group by `org_chart`.`level` order by `org_chart`.`level`"
    },
    {
      "lesson": "tmp",
      "match": {
        "folded_label": "MATERIALIZE starts=1 rows=1000",
        "short_label": "Materialize"
      },
      "controls": {
        "rows": 1000,
        "row_size": 200
      },
      "note": "Materialize recursive CTE org_chart",
      "query_sql": "with recursive `org_chart` as (/* select#2 */ select `demodb`.`employees`.`id` AS `id`,`demodb`.`employees`.`name` AS `name`,`demodb`.`employees`.`manager_id` AS `manager_id`,1 AS `level` from `demodb`.`employees` where (`demodb`.`employees`.`manager_id` is null) union all /* select#3 */ select `e`.`id` AS `id`,`e`.`name` AS `name`,`e`.`manager_id` AS `manager_id`,(`oc`.`level` + 1) AS `oc.level + 1` from `demodb`.`employees` `e` join `org_chart` `oc` where ((`e`.`manager_id` = `oc`.`id`) and (`oc`.`level` < 10))) /* select#1 */ select `org_chart`.`level` AS `level`,count(0) AS `headcount` from `org_chart` group by `org_chart`.`level` order by `org_chart`.`level`"
    },
    {
      "lesson": "non_unique_lookup",
      "match": {
        "folded_label": "INDEX LOOKUP [employees.idx_mgr] starts=1 rows=100",
        "short_label": "Index lookup [employees.idx_mgr]"
      },
      "controls": {
        "rows": 100,
        "selectivity": 40.0,
        "covering": false
      },
      "note": "Index lookup on employees using idx_mgr (manager_id=NULL), with index condition: (employees.manager_id is null) (employees)",
      "query_sql": "with recursive `org_chart` as (/* select#2 */ select `demodb`.`employees`.`id` AS `id`,`demodb`.`employees`.`name` AS `name`,`demodb`.`employees`.`manager_id` AS `manager_id`,1 AS `level` from `demodb`.`employees` where (`demodb`.`employees`.`manager_id` is null) union all /* select#3 */ select `e`.`id` AS `id`,`e`.`name` AS `name`,`e`.`manager_id` AS `manager_id`,(`oc`.`level` + 1) AS `oc.level + 1` from `demodb`.`employees` `e` join `org_chart` `oc` where ((`e`.`manager_id` = `oc`.`id`) and (`oc`.`level` < 10))) /* select#1 */ select `org_chart`.`level` AS `level`,count(0) AS `headcount` from `org_chart` group by `org_chart`.`level` order by `org_chart`.`level`"
    },
    {
      "lesson": "nested_loop",
      "match": {
        "folded_label": "NESTED LOOP INNER starts=2 rows=450",
        "short_label": "Nested loop inner join"
      },
      "controls": {
        "outer_rows": 900,
        "inner_rows": 900,
        "row_size": 200
      },
      "note": "Nested loop inner join",
      "query_sql": "with recursive `org_chart` as (/* select#2 */ select `demodb`.`employees`.`id` AS `id`,`demodb`.`employees`.`name` AS `name`,`demodb`.`employees`.`manager_id` AS `manager_id`,1 AS `level` from `demodb`.`employees` where (`demodb`.`employees`.`manager_id` is null) union all /* select#3 */ select `e`.`id` AS `id`,`e`.`name` AS `name`,`e`.`manager_id` AS `manager_id`,(`oc`.`level` + 1) AS `oc.level + 1` from `demodb`.`employees` `e` join `org_chart` `oc` where ((`e`.`manager_id` = `oc`.`id`) and (`oc`.`level` < 10))) /* select#1 */ select `org_chart`.`level` AS `level`,count(0) AS `headcount` from `org_chart` group by `org_chart`.`level` order by `org_chart`.`level`"
    },
    {
      "lesson": "filter",
      "match": {
        "folded_label": "FILTER (((oc.level < 10) and (oc.id is not null))) starts=2 rows=450",
        "short_label": "Filter: (((oc.level < 10) and (oc.id is not null)))"
      },
      "controls": {
        "input_rows": 900,
        "selectivity": 10.0
      },
      "note": "Filter: ((oc.`level` < 10) and (oc.id is not null))",
      "query_sql": "with recursive `org_chart` as (/* select#2 */ select `demodb`.`employees`.`id` AS `id`,`demodb`.`employees`.`name` AS `name`,`demodb`.`employees`.`manager_id` AS `manager_id`,1 AS `level` from `demodb`.`employees` where (`demodb`.`employees`.`manager_id` is null) union all /* select#3 */ select `e`.`id` AS `id`,`e`.`name` AS `name`,`e`.`manager_id` AS `manager_id`,(`oc`.`level` + 1) AS `oc.level + 1` from `demodb`.`employees` `e` join `org_chart` `oc` where ((`e`.`manager_id` = `oc`.`id`) and (`oc`.`level` < 10))) /* select#1 */ select `org_chart`.`level` AS `level`,count(0) AS `headcount` from `org_chart` group by `org_chart`.`level` order by `org_chart`.`level`"
    },
    {
      "lesson": "non_unique_lookup",
      "match": {
        "folded_label": "INDEX LOOKUP [employees.idx_mgr] starts=900 rows=1",
        "short_label": "Index lookup [employees.idx_mgr]"
      },
      "controls": {
        "rows": 900,
        "selectivity": 40.0,
        "covering": false
      },
      "note": "Index lookup on e using idx_mgr (manager_id=oc.id) (employees)",
      "query_sql": "with recursive `org_chart` as (/* select#2 */ select `demodb`.`employees`.`id` AS `id`,`demodb`.`employees`.`name` AS `name`,`demodb`.`employees`.`manager_id` AS `manager_id`,1 AS `level` from `demodb`.`employees` where (`demodb`.`employees`.`manager_id` is null) union all /* select#3 */ select `e`.`id` AS `id`,`e`.`name` AS `name`,`e`.`manager_id` AS `manager_id`,(`oc`.`level` + 1) AS `oc.level + 1` from `demodb`.`employees` `e` join `org_chart` `oc` where ((`e`.`manager_id` = `oc`.`id`) and (`oc`.`level` < 10))) /* select#1 */ select `org_chart`.`level` AS `level`,count(0) AS `headcount` from `org_chart` group by `org_chart`.`level` order by `org_chart`.`level`"
    }
  ],
  "operator_complexities": [
    {
      "node_id": "n:347eeced0b3c",
      "folded_label": "SORT starts=1 rows=10",
      "short_label": "Sort",
      "complexity": {
        "big_o": "O(n log n)",
        "short": "n log n",
        "severity": "medium",
        "rationale": "Filesort: MySQL sorts the output in the server layer. Priority-queue filesort (LIMIT) is not detected today — we report the upper bound.",
        "confidence": "worst_case",
        "learn_more": "filesort"
      }
    },
    {
      "node_id": "n:ca439ce5f715",
      "folded_label": "TABLE SCAN [<temporary>] starts=1 rows=10",
      "short_label": "Table scan [<temporary>]",
      "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:23956a72ce60",
      "folded_label": "TABLE SCAN [org_chart] starts=1 rows=1000",
      "short_label": "Table scan [org_chart]",
      "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:0d118da99553",
      "folded_label": "MATERIALIZE starts=1 rows=1000",
      "short_label": "Materialize",
      "complexity": {
        "big_o": "build: O(log n + k)  •  scan: O(1) per probe, O(rows) to fetch all",
        "short": "build+scan",
        "severity": "good",
        "rationale": "Materialize is a two-phase operator. The build pays the inner subquery's cost ONCE; each scan of the temp table is effectively constant time. Don't compound build cost by loops.",
        "confidence": "exact",
        "learn_more": "materialization",
        "build_complexity": {
          "big_o": "O(log n + k)",
          "short": "log n + k",
          "severity": "good",
          "rationale": "Index lookup (ref): one B+tree descent then a sequential walk over k matching index entries. k = matching rows for the indexed predicate.",
          "confidence": "exact",
          "learn_more": "index_lookup"
        },
        "scan_complexity": {
          "big_o": "O(1) per probe, O(rows) to fetch all",
          "short": "O(1)/probe",
          "severity": "good",
          "rationale": "Materialize scan: reads the pre-computed temp table. Each probe is constant time once the table is in memory.",
          "confidence": "typical",
          "learn_more": "materialization"
        }
      }
    },
    {
      "node_id": "n:690d9483b0a5",
      "folded_label": "INDEX LOOKUP [employees.idx_mgr] starts=1 rows=100",
      "short_label": "Index lookup [employees.idx_mgr]",
      "complexity": {
        "big_o": "O(log n + k)",
        "short": "log n + k",
        "severity": "good",
        "rationale": "Index lookup (ref): one B+tree descent then a sequential walk over k matching index entries. k = matching rows for the indexed predicate.",
        "confidence": "exact",
        "learn_more": "index_lookup"
      }
    },
    {
      "node_id": "n:b76c70582649",
      "folded_label": "NESTED LOOP INNER starts=2 rows=450",
      "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:911d971460cb",
      "folded_label": "INDEX LOOKUP [employees.idx_mgr] starts=900 rows=1",
      "short_label": "Index lookup [employees.idx_mgr]",
      "complexity": {
        "big_o": "O(log n + k)",
        "short": "log n + k",
        "severity": "good",
        "rationale": "Index lookup (ref): one B+tree descent then a sequential walk over k matching index entries. k = matching rows for the indexed predicate.",
        "confidence": "exact",
        "learn_more": "index_lookup"
      }
    }
  ]
}

Teach: Operator deep dive