1. Turn on the slow query log
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1; -- seconds
SET GLOBAL log_queries_not_using_indexes = OFF; -- ON is very noisy
SHOW VARIABLES LIKE 'slow_query_log_file';
Managed services: set these in the parameter group (RDS) or server parameters (Azure); the logs appear in the console or CloudWatch/Log Analytics when exported.
2. Summarise it
mysqldumpslow -s t -t 10 /var/lib/mysql/host-slow.log # top 10 by total time
pt-query-digest /var/lib/mysql/host-slow.log > digest.txt # Percona Toolkit, more detail
Sort by total time (count × average), not just the slowest single query. A 200 ms query run 50,000 times an hour matters more than one 30-second report.
3. Live view without the log: sys schema
SELECT query, exec_count, total_latency, avg_latency, rows_examined_avg, rows_sent_avg
FROM sys.statement_analysis
ORDER BY total_latency DESC LIMIT 10;
SELECT * FROM sys.statements_with_full_table_scans ORDER BY total_latency DESC LIMIT 10;
SELECT * FROM sys.schema_unused_indexes;
rows_examined_avg far larger than rows_sent_avg → the query reads many rows to return a few. That's an index problem.
4. EXPLAIN
EXPLAIN FORMAT=TREE
SELECT * FROM orders WHERE customer_id = 42 AND status = 'OPEN' ORDER BY created_at DESC LIMIT 20;
EXPLAIN ANALYZE SELECT ...; -- 8.0.18+, actually runs the query
| In EXPLAIN | Meaning |
|---|---|
type: ALL | Full table scan |
key: NULL | No index used |
Using filesort | Extra sort step; an index could deliver the order |
Using temporary | Temp table for GROUP BY/DISTINCT |
rows very large | Many rows examined |
5. Add the right index
For the query above: equality columns first, then the sort column.
ALTER TABLE orders ADD INDEX idx_cust_status_created (customer_id, status, created_at), ALGORITHM=INPLACE, LOCK=NONE;
For very large tables in production, use an online schema change tool (gh-ost, pt-online-schema-change) to control load and replica lag.
6. Common query fixes
WHERE DATE(created_at) = '2026-10-22'→WHERE created_at >= '2026-10-22' AND created_at < '2026-10-23'.LIKE '%term%'can't use a B-tree index. Consider a FULLTEXT index.- Comparing a string column to a number (
WHERE phone = 98765) disables the index. Quote the value. SELECT *on wide tables withORDER BY ... LIMIT→ select only needed columns.
Tip
Before adding an index, check
sys.schema_redundant_indexes. Every extra index slows writes; replacing two overlapping indexes with one often helps both reads and writes.