The MySQL slow query log records every query that takes longer than long_query_time seconds to execute. It is the primary tool for identifying which queries are consuming the most time on a production server and is the starting point for any query optimization effort.
Impact
Without the slow query log enabled, the only way to catch slow queries is real-time monitoring of SHOW PROCESSLIST or performance_schema.events_statements_current, both of which require a query to be running right now. The slow query log provides a persistent, aggregatable record of historical performance — essential for diagnosing intermittent slowdowns that do not coincide with when a developer is watching.
Common Causes of Slow Queries Being Logged
- Full table scans due to missing or unused indexes
- Large result sets returned without
LIMIT - Queries running inside long transactions that spend time waiting for locks
ORDER BYandGROUP BYoperations that cannot use an index and spill to a temporary tableJOINwithout an index on the join column causing nested-loop full scans- Subqueries that are not optimized to use a semi-join or materialization strategy
- High
long_query_timethreshold that masks moderately slow queries at P95 log_queries_not_using_indexes=ONlogging short queries that happen to skip indexes (useful but noisy)
Configuration and Enabling the Slow Query Log
Check current settings:
SHOW VARIABLES LIKE 'slow_query_log%'; SHOW VARIABLES LIKE 'long_query_time'; SHOW VARIABLES LIKE 'log_queries_not_using_indexes'; SHOW VARIABLES LIKE 'min_examined_row_limit';Enable the slow query log at runtime (no restart required):
SET GLOBAL slow_query_log = ON; SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log'; -- Log queries taking longer than 1 second SET GLOBAL long_query_time = 1; -- Also log queries that don't use indexes SET GLOBAL log_queries_not_using_indexes = ON; -- Suppress logging of trivial full-scans on tiny tables SET GLOBAL min_examined_row_limit = 100; -- Persist across restarts (MySQL 8.0+) SET PERSIST slow_query_log = ON; SET PERSIST long_query_time = 1; SET PERSIST log_queries_not_using_indexes = ON; SET PERSIST min_examined_row_limit = 100;In
my.cnf:[mysqld] slow_query_log = 1 slow_query_log_file = /var/log/mysql/slow.log long_query_time = 1 log_queries_not_using_indexes = ON min_examined_row_limit = 100Tune
long_query_timeto your SLA. A value of 1 second is a useful starting threshold; lower to 0.1 (100 ms) on well-tuned systems to catch the next tier of slow queries. Setting it to 0 logs everything — useful for load testing but too noisy for production.Enable logging of administrative statements (optional):
SET GLOBAL log_slow_admin_statements = ON;This captures slow
ALTER TABLE,OPTIMIZE TABLE,ANALYZE TABLE, and similar DDL operations.
Reading the Slow Query Log
A typical entry looks like:
# Time: 2025-04-10T14:23:07.123456Z
# User@Host: app[app] @ 10.0.1.5 [10.0.1.5] Id: 4521
# Query_time: 4.231891 Lock_time: 0.000112 Rows_sent: 1 Rows_examined: 1847293
SET timestamp=1744294987;
SELECT * FROM orders WHERE customer_email = 'alice@example.com' ORDER BY created_at DESC LIMIT 1;
Key fields:
Query_time— wall-clock seconds the query tookLock_time— time spent waiting for locks (subtracting this gives pure execution time)Rows_sent— rows returned to the clientRows_examined— rows read from storage to produce the result; a highRows_examined / Rows_sentratio indicates a missing index or poor predicate
Aggregating and Analyzing the Log
Use
mysqldumpslow(built-in) for a quick summary:# Top 10 queries by total query time mysqldumpslow -s t -t 10 /var/log/mysql/slow.log # Top 10 by average query time mysqldumpslow -s at -t 10 /var/log/mysql/slow.log # Top 10 by total rows examined mysqldumpslow -s r -t 10 /var/log/mysql/slow.logmysqldumpslownormalizes parameter values so similar queries are grouped together.Use
pt-query-digest(Percona Toolkit) for production analysis:pt-query-digest /var/log/mysql/slow.log > digest.txtOutput shows each normalized query fingerprint with:
- Total time consumed
- Query count
- Min / Avg / Max / P95 / P99 latency
- Rows examined vs. rows sent ratio
- The worst example query
Filter to the last hour:
pt-query-digest --since=3600 /var/log/mysql/slow.logSend directly to a MySQL table for dashboarding:
pt-query-digest --review h=localhost,D=percona,t=query_review \ --history h=localhost,D=percona,t=query_history \ /var/log/mysql/slow.logUse
performance_schemaas an alternative (no log file required):-- Top 10 queries by total time (MySQL 8.0 with performance_schema enabled) SELECT DIGEST_TEXT, COUNT_STAR AS calls, ROUND(SUM_TIMER_WAIT / 1e12, 3) AS total_s, ROUND(AVG_TIMER_WAIT / 1e12, 3) AS avg_s, SUM_ROWS_EXAMINED, SUM_ROWS_SENT, ROUND(SUM_ROWS_EXAMINED / NULLIF(SUM_ROWS_SENT, 0), 1) AS rows_examined_ratio FROM performance_schema.events_statements_summary_by_digest ORDER BY total_s DESC LIMIT 10;Reset statistics after tuning to measure improvement:
TRUNCATE performance_schema.events_statements_summary_by_digest;
Acting on Findings
Run
EXPLAINon the worst queries:EXPLAIN SELECT * FROM orders WHERE customer_email = 'alice@example.com' ORDER BY created_at DESC LIMIT 1;Look for:
type: ALL— full table scanExtra: Using filesort— cannot use an index forORDER BYExtra: Using temporary— required a temp table forGROUP BYorDISTINCTrows— estimated rows examined; compare to actualRows_examinedin the log
Use
EXPLAIN FORMAT=JSONfor a detailed cost breakdown:EXPLAIN FORMAT=JSON SELECT ...;Add missing indexes:
-- For a query filtering on customer_email and ordering by created_at ALTER TABLE orders ADD INDEX idx_email_created (customer_email, created_at); -- Then verify the index is used EXPLAIN SELECT * FROM orders WHERE customer_email = 'alice@example.com' ORDER BY created_at DESC LIMIT 1;Review
log_queries_not_using_indexesoutput carefully. Small tables that are always faster with a full scan will appear here even though they are not a problem. Usemin_examined_row_limit = 1000to suppress trivial cases.Rotate and manage the slow log file to prevent unbounded growth:
-- In MySQL 8.0, flush to rotate FLUSH SLOW LOGS;Configure logrotate:
/var/log/mysql/slow.log { daily rotate 7 missingok notifempty delaycompress compress postrotate /usr/bin/mysqladmin flush-logs endscript }
Additional Information
- The slow query log captures wall-clock time, not CPU time. A query that is fast on CPU but waits 3 seconds for a row lock will appear in the log. Use
Lock_timeto distinguish lock waits from actual query slowness. performance_schema.events_statements_summary_by_digestaggregates by normalized fingerprint automatically and does not require disk I/O for each query. It is the preferred approach on servers where disk is a bottleneck.- For Amazon RDS and Aurora, the slow query log can be published to CloudWatch Logs, enabling alerting and full-text search without SSH access to the server.
long_query_timesupports sub-second values:SET GLOBAL long_query_time = 0.5logs queries > 500 ms.- The
Rows_examinedvalue in the slow log corresponds to theHandler_read_*status variables. A high ratio of rows examined to rows sent is the single best indicator of a missing index.
Frequently Asked Questions
Q: Will enabling the slow query log slow down my database? A: Minimally. Each slow query write appends a few hundred bytes to a log file. The overhead is proportional to the number of slow queries — if you have many, the writes add up, but those queries are already hurting performance. Writing to a file on the same disk as data is an anti-pattern on busy servers; use a separate volume or pipe to a remote logging service.
Q: long_query_time = 0 logs everything. Is that safe?
A: Safe but extremely noisy. On a busy server with 10,000 QPS, you will produce gigabytes of log per hour. Use performance_schema aggregation instead for full-spectrum visibility without log overhead.
Q: My slow queries have Lock_time of 3 seconds but Query_time of 3.001 seconds. Which one should I fix?
A: The lock wait. The query itself is fast (0.001 s execution); the bottleneck is another transaction holding a lock. Investigate the blocker using sys.innodb_lock_waits or information_schema.INNODB_TRX.
Q: How do I find slow queries on Amazon RDS without server access?
A: Enable the slow query log parameter (slow_query_log = 1) and set log_output = FILE in the RDS parameter group. Then use the AWS Console or CLI to download the log from the RDS console, or publish logs to CloudWatch Logs for real-time querying.
Q: pt-query-digest shows a query called administrator command: Connect. What is that?
A: That represents the time for connection establishment, not a real query. If it appears frequently and high in the ranking, your application is opening and closing connections constantly — use a connection pool.