High CPU is a symptom, not a diagnosis. A busy MySQL server may be doing useful work, repeatedly scanning too much data, spending time in a poor join order, or carrying a connection storm. Scaling the instance before identifying the work can make the bill larger while leaving the latency pattern intact.
Establish whether CPU is truly the limiting resource
Compare CPU with response time, throughput, active sessions and disk activity over the same interval. CPU that rises with completed work but stable latency can be healthy. CPU that stays high while throughput flattens, queues grow or application latency climbs needs investigation. Look for a step change after a deployment, index change, batch job or traffic event rather than treating a daily peak as automatically abnormal.
Identify the expensive work
Rank statements by total execution time, execution count and rows examined, then inspect the same statements during a quiet interval. A query can be cheap per call but become the main consumer when called thousands of times. Conversely, one report may dominate elapsed time without explaining a broad CPU plateau. Use the execution plan to check access paths, join order, estimated versus actual row counts where available, temporary tables and filesorts.
- Check whether the active thread count grows with CPU.
- Separate application traffic from maintenance, ETL and reporting jobs.
- Check for full scans caused by non-sargable predicates or missing composite indexes.
- Confirm that an index would reduce reads, not merely add write cost.
Make a safe change
Start with the smallest defensible change: rewrite a predicate, add or adjust an index after checking selectivity, batch an oversized operation, or cap unnecessary parallel application work. Capture a before-and-after window. The goal is not merely a lower CPU percentage; it is lower latency or more sustainable throughput without creating lock, I/O or replication side effects.
When scaling is the right answer
Scale after you can show that the workload is efficient and capacity is still insufficient. Forecast from sustained demand, peak concurrency and headroom, then retest the critical queries. A larger instance may be the correct operational decision, but it should follow evidence rather than replace it.
Example: a search endpoint overwhelms a warm database
Suppose a customer search endpoint becomes slow after its traffic doubles. The illustrative numbers are 800 calls per minute, 40,000 rows examined per call and 20 rows returned. Disk reads remain low because the data is cached. This is still an expensive access pattern: keeping data in memory does not remove the CPU cost of checking millions of candidate rows. The investigation should start with the predicate and index, not the buffer pool.
On MySQL 8.0 or 8.4 with the relevant Performance Schema collection enabled, inspect statement summaries by digest. Compare COUNT_STAR, SUM_TIMER_WAIT and SUM_ROWS_EXAMINED between two observations. These are accumulated values; the largest lifetime total may simply belong to the oldest busy query. Use elapsed time as a candidate ranking, not as a direct measurement of CPU consumption.
SELECT DIGEST_TEXT, COUNT_STAR, SUM_ROWS_EXAMINED,
SUM_TIMER_WAIT
FROM performance_schema.events_statements_summary_by_digest
WHERE DIGEST_TEXT IS NOT NULL
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;
What a useful plan comparison looks like
Save the query with representative parameters and run EXPLAIN. A function around a filtered column, an implicit conversion or an index whose leading columns do not match the lookup may explain the excess work. Compare the proposed access path with the current one, including how much data the application actually needs. Pagination that repeatedly discards a huge offset may need a different query design rather than another index.
EXPLAIN ANALYZE executes the statement on supported versions. Use a controlled environment for expensive examples and account for its measurement overhead. Do not replay a production write or a heavy report simply to obtain a more attractive plan display.
Define success per request
For this example, record rows examined per search, database time per search, calls per minute and application p95 latency. If CPU falls only because fewer requests complete, the change has failed. Also test write throughput and index storage: the new index must earn its ongoing maintenance cost. Keep the old plan and workload parameters with the deployment record so a later regression can be compared fairly.
Keep the evidence for the next incident
Mini DBA MySQL monitoring brings query and metric history together with InnoDB and connection diagnostics for investigating recurring incidents. Available evidence depends on the monitored version, permissions and configured collection.
References and further reading