EXPLAIN ANALYZE tells you what happened, not what might

EXPLAIN shows the plan the optimiser chose and its estimates, and the estimates are frequently wrong — a query planned for 40 rows that reads 400,000 looks fine in the plan.

EXPLAIN ANALYZE
SELECT c.name, COUNT(*) FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.placed_at > '2018-01-01' GROUP BY c.id;

-- -> Nested loop inner join  (cost=8241 rows=8100)
--      (actual time=0.09..412 rows=94012 loops=1)
--                                  ^^^^^^^^ estimate was 8,100

The gap between rows= and actual rows= is the diagnosis: a large discrepancy means the statistics are stale or the predicate is one the optimiser cannot estimate, and both have known fixes. It actually runs the query, so it is not something to point at a DELETE. MariaDB has ANALYZE with the same idea and different output, which is one more place the two have diverged.