QueryTuning.org
Database Query Performance Reference
MySQLPerformanceSlow Query Log

The Slow Query Log Saved Our Migration. Here's How to Actually Read It.

AM
Ananya Menon
MySQL DBA · 9 years production
March 5, 2026
8 min read
MySQL 5.6+ / MariaDB 10

The slow query log is the most direct instrument for measuring query performance in production. It captures every query that exceeds a time threshold, including execution time, rows examined, rows sent, lock time, and the full query text. What it captures is a running record of every time MySQL spent more than N seconds doing something. What most teams do with it is almost nothing.

Enabling the Slow Query Log

Enable without restarting MySQL

All slow query log settings can be changed at runtime with SET GLOBAL. You do not need a server restart or a maintenance window to enable logging.

Enable slow query logging at runtime
MySQL
-- Enable slow query logging immediately:
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';

-- Set the threshold (seconds):
-- 1.0 = capture all queries over 1 second
-- 0.1 = capture all queries over 100ms (for performance tuning)
-- 0   = capture ALL queries (only for short diagnostic windows)
SET GLOBAL long_query_time = 1;

-- Also capture queries that do not use indexes (often the most important ones):
SET GLOBAL log_queries_not_using_indexes = 'ON';

-- Verify settings took effect:
SHOW VARIABLES LIKE 'slow%';
SHOW VARIABLES LIKE 'long_query_time';
SHOW VARIABLES LIKE 'log_queries_not_using_indexes';

Reading the Raw Log

The raw slow query log is a flat text file. Each entry contains the query and its statistics. The format is consistent but not easily parseable by eye at volume:

# Time: 2024-11-14T02:17:44.882943Z
# User@Host: appuser[appuser] @ web-01 [10.0.1.44]
# Query_time: 4.281734  Lock_time: 0.000182  Rows_sent: 1  Rows_examined: 8492341
SET timestamp=1731550664;
SELECT * FROM orders WHERE status = 'pending' ORDER BY created_at LIMIT 1;

The critical fields: Query_time is total execution time. Rows_examined is how many rows MySQL read to produce the result. Rows_sent is how many rows were returned. When Rows_examined is 8 million and Rows_sent is 1, MySQL is doing a full table scan to find one row — the most common cause of slow queries.

Aggregating with mysqldumpslow

Find the worst queries by total time spent
MySQL
# -s t: sort by total time (sum of all executions)
# -t 20: show top 20 queries
# -a: show real numbers, not averages
# Normalises queries by replacing literal values with N or S

mysqldumpslow -s t -t 20 -a /var/log/mysql/slow.log

# Output format:
# Count: 1847  Time=0.18s (328s)  Lock=0.00s (0s)  Rows=1.0 (1847)
# SELECT * FROM orders WHERE status = 'S' ORDER BY created_at LIMIT N
#
# Read as: ran 1847 times, averaged 0.18s, total 328 seconds consumed
# The query normalised "pending" → S and 1 → N
Find the worst queries by rows examined (missing indexes)
MySQL
# -s r: sort by average rows examined
# These are your worst missing-index candidates
mysqldumpslow -s r -t 10 /var/log/mysql/slow.log

Going Deeper with pt-query-digest

pt-query-digest from Percona Toolkit produces far richer analysis than mysqldumpslow. It fingerprints queries, groups variations, and shows full percentile distributions for each query pattern.

Full analysis with pt-query-digest
MySQL
# Install: apt install percona-toolkit (Debian/Ubuntu)
#          yum install percona-toolkit (RHEL/CentOS)

# Full analysis — writes a report sorted by total time:
pt-query-digest /var/log/mysql/slow.log > /tmp/slow_report.txt

# Most important output sections:
# 1. "Overall" — total time, unique query patterns, total queries
# 2. "Profile" — ranked list of queries by total time consumed
# 3. Per-query detail — pct of total, concurrency, p95/p99 latency

# Common patterns to look for:
# Exec time: 4.28s avg, 29.1s max  → high variance = parameter-sensitive plan
# Rows examine: 8.49M avg           → full table scan
# Rows sent:    1 avg               → one row returned from 8M read
# Lock time: 12ms avg               → lock contention adding latency

Using EXPLAIN on Slow Queries

EXPLAIN ANALYZE on the worst query
MySQL
-- Take the worst query from the log and EXPLAIN it:
-- (replace literal values with real values for the EXPLAIN)
EXPLAIN SELECT * FROM orders
WHERE  status = 'pending'
ORDER BY created_at
LIMIT  1;

-- Key columns to read:
-- type: 'ALL' = full table scan (bad), 'ref' = index lookup (good)
-- rows: estimated rows examined. Match against Rows_examined in log.
-- Extra: 'Using filesort' = sort in memory/disk (expensive)
--        'Using index' = covering index (fast)
--        'Using where' = filter applied after index

-- For full execution details (MySQL 8.0):
EXPLAIN ANALYZE SELECT * FROM orders
WHERE  status = 'pending'
ORDER BY created_at
LIMIT  1;

-- EXPLAIN ANALYZE actually runs the query and shows actual rows/loops
Fix: create a composite index for the slow query pattern
MySQL
-- For: WHERE status = 'pending' ORDER BY created_at
-- The correct index covers both the filter and the sort:
CREATE INDEX idx_orders_status_created
  ON orders (status, created_at);

-- Why this order matters:
-- status first: allows index seek on status = 'pending'
-- created_at second: rows already sorted within each status group
-- Without this order: MySQL would seek on status, then filesort on created_at

Using performance_schema Instead of the Log

Find the worst queries live without a log file
MySQL
SELECT
  DIGEST_TEXT                                          AS query_pattern,
  COUNT_STAR                                           AS executions,
  ROUND(SUM_TIMER_WAIT/1e12, 3)                       AS total_sec,
  ROUND(AVG_TIMER_WAIT/1e12, 3)                       AS avg_sec,
  ROUND(MAX_TIMER_WAIT/1e12, 3)                       AS max_sec,
  SUM_ROWS_EXAMINED,
  SUM_ROWS_SENT,
  SUM_NO_INDEX_USED + SUM_NO_GOOD_INDEX_USED           AS no_index_count
FROM   performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT  20;

-- This is available without enabling the slow query log
-- Persists across connections, reset with:
-- TRUNCATE TABLE performance_schema.events_statements_summary_by_digest

The Correct Analysis Workflow

The workflow that finds the highest-impact problems fastest: enable the log with a 1-second threshold, let it run for 24 hours covering a full business cycle, run pt-query-digest sorted by total time, take the top 3 queries by total time, run EXPLAIN ANALYZE on each one, create the appropriate index or rewrite the query. Repeat weekly until the total time in the slow log drops to an acceptable baseline.

The mistake most teams make: they look at the slowest individual query, not the queries consuming the most total time. A 30-second query that runs once a day costs 30 seconds. A 2-second query that runs 10,000 times a day costs 5.5 hours. pt-query-digest's default sort — total time — finds the right problem automatically.

AM
Ananya Menon
MySQL DBA · 9 years production · QueryTuning
Ananya has spent 9 years managing MySQL and MariaDB for SaaS companies. She specialises in query optimisation, slow query analysis, and schema migration strategy.