PATTERN Cited by 1 source
Stage-level query profiling¶
Problem. A single query takes ~700 ms. You need to know
what it's slow at — optimising? opening tables? executing
the plan? waiting on a lock? waiting on handler commit? —
before deciding what to fix. EXPLAIN
ANALYZE answers which iterator was expensive; it does
not answer which server-lifecycle stage consumed the
wall-clock.
Solution. Use MySQL's
stage-timing
instrumentation in performance_schema:
- Enable the three toggles (
setup_instruments,setup_consumers,setup_actors) — either globally, or scoped to a specific test user to limit overhead. - Capture the connection's
thread_idfromperformance_schema.threads. - Execute the query.
- Query
events_stages_history_longbetween the statement'sevent_idandend_event_idto get a per-stage millisecond breakdown.
(Source: sources/2026-04-21-planetscale-identifying-and-profiling-problematic-mysql-queries.)
Canonical stages to look at¶
stage/sql/executing— running the plan. Dominance here →EXPLAIN ANALYZEis the next step.stage/sql/optimizing+stage/sql/statistics+stage/sql/preparing— query planning. Dominance here suggests stale stats, missing histograms, or planner bugs.stage/sql/Opening tables+stage/sql/System lock— table-level locking / metadata acquisition. Dominance suggests DDL contention or too many tables.stage/sql/waiting for handler commit— InnoDB commit path. Dominance suggests durability-setting issues or log flush contention.stage/sql/checking permissions— auth path. Dominance suggests grant-table scans (rare but real).
Worked example¶
From the PlanetScale post:
stage/sql/executing | 735.3020 ms
stage/sql/statistics | 0.1040 ms
stage/sql/Opening tables | 0.0950 ms
stage/sql/preparing | 0.0590 ms
stage/sql/optimizing | 0.0270 ms
(all others < 0.01 ms)
Total ~736 ms, >99% in executing. Reading this profile
tells you "planning is fine, locks are fine, the plan
itself is slow — go look at EXPLAIN ANALYZE or the
rows-examined."
Enablement cost¶
Global enablement has "(small) adverse effect on the overall performance of your system" per PlanetScale — all sessions pay the instrumentation tax.
Selective enablement via setup_actors:
UPDATE performance_schema.setup_actors
SET ENABLED = 'NO', HISTORY = 'NO'
WHERE HOST = '%' AND USER = '%';
INSERT INTO performance_schema.setup_actors
(HOST, USER, ROLE, ENABLED, HISTORY)
VALUES ('your_host', 'your_user', '%', 'YES', 'YES');
Scopes profiling to a single principal — the one running the test queries.
Caveats¶
- In-memory bounded history —
events_stages_history_longis a ring buffer; old statements age out. Run the profile workflow immediately after the query. - Overhead — enabling every instrument has measurable cost in high-QPS workloads. Scope narrowly.
- Timer arithmetic —
(timer_end - timer_start) / 1000000000gives milliseconds (source is picoseconds). - Doesn't replace
EXPLAIN ANALYZE— they're complementary. Stage profiling answers "where in the server lifecycle";EXPLAIN ANALYZEanswers "where in the plan." - Production applicability — enabling history on prod shared hosts is discouraged; use a replica or a scoped-user on a staging instance.
- Setup complexity — four SQL blocks + session variables;
not ergonomic. Tools like
PlanetScale Insights
package this into a UI (for the digest-level identification
step; per-execution stage profiling still requires manual
performance_schemaqueries even with Insights).
Seen in¶
- PlanetScale's field manual for query diagnosis
(2024-03-29). Dicken walks through the full four-step
workflow with the worked 735.3 ms
executing-dominance example. Pairs the pattern with patterns/digest-based-query-prioritization (identify which query to profile) and concepts/mysql-explain-analyze (drill into the plan once stage profiling points toexecuting). (Source: sources/2026-04-21-planetscale-identifying-and-profiling-problematic-mysql-queries.)