Skip to content
Back to Blog
Performance10 min read

Common Mistakes Finding and Fixing Slow MySQL Queries

Troubleshooting slow MySQL queries often fails because of avoidable mistakes. Learn the correct approach to profiling, indexing, and query optimization that actually works.

Written by Abdul AbrorTechnical Hosting Support Engineer
Common Mistakes Finding and Fixing Slow MySQL Queries
On this page

Slow MySQL queries are a common source of performance issues in web hosting environments, but the way most people approach diagnosing and fixing them is fundamentally flawed. I've seen countless WordPress sites, cPanel databases, and custom applications struggle with performance because developers and sysadmins make the same preventable mistakes when investigating query slowness. This guide walks through the most common errors and shows you the correct approach for each.

Mistake 1: Not Enabling or Checking the Slow Query Log

Many troubleshooting efforts fail at the first step because the slow query log isn't enabled or configured properly. Without this log, you're guessing which queries are causing problems rather than measuring actual performance.

The Wrong Approach

Running SHOW PROCESSLIST repeatedly and hoping to catch a slow query in action. This approach is reactive, intermittent, and misses queries that run frequently but just under your observation window.

The Correct Approach

Enable the slow query log to capture problem queries automatically:

SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 2;
SET GLOBAL log_queries_not_using_indexes = 'ON';

For persistent configuration, add these lines to your my.cnf or my.ini:

[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 2
log_queries_not_using_indexes = 1

Set long_query_time based on your application requirements. Two seconds is a reasonable starting point for most web applications, but adjust based on your specific performance targets.

After queries accumulate, use mysqldumpslow to analyze the log:

mysqldumpslow -s t -t 10 /var/log/mysql/mysql-slow.log

This shows the top 10 slowest queries by total time, which is usually more useful than sorting by average time for identifying the biggest performance impacts.

Mistake 2: Optimizing Queries Without Understanding Execution Plans

The second most common mistake is attempting to fix a query without understanding how MySQL actually executes it. People add indexes randomly or rewrite queries based on guesswork rather than data.

The Wrong Approach

Seeing a slow query and immediately adding indexes to every column mentioned in the WHERE clause, or blindly rewriting JOINs without measuring the impact.

The Correct Approach

Use EXPLAIN or EXPLAIN ANALYZE to see exactly how MySQL processes your query:

EXPLAIN SELECT u.username, p.title 
FROM users u 
JOIN posts p ON u.id = p.user_id 
WHERE u.status = 'active' 
AND p.created_at > '2026-01-01';

Key columns to examine in the output:

  • type: Look for ALL (full table scan) or index (full index scan). These indicate missing or unused indexes. Aim for ref, eq_ref, or range.
  • possible_keys: Shows which indexes MySQL considered.
  • key: Shows which index MySQL actually used. If this is NULL, no index was used.
  • rows: Estimated number of rows examined. High numbers indicate inefficiency.
  • Extra: Look for "Using filesort" or "Using temporary" which indicate expensive operations.

For even more detail, use EXPLAIN ANALYZE which actually executes the query and shows real timing data:

EXPLAIN ANALYZE SELECT ...

Only after understanding the execution plan should you consider changes. If you see a full table scan where an index should exist, that's when you add the index.

Mistake 3: Creating Redundant or Incorrect Indexes

Once people discover indexes solve slow queries, they often over-index or create indexes that MySQL won't actually use effectively.

The Wrong Approach

Adding single-column indexes for every column in a complex WHERE clause, or creating an index on columns in the wrong order for your query patterns.

The Correct Approach

Understand that MySQL can typically use only one index per table per query (with some exceptions for index merges). For multi-column WHERE clauses, create composite indexes with the most selective columns first:

-- If you query: WHERE status = 'active' AND created_at > '2026-01-01'
-- And status has few distinct values but created_at is highly selective:
CREATE INDEX idx_created_status ON posts(created_at, status);

Follow the "equality first, range second" rule for composite indexes. Columns used with = should come before columns used with >, <, BETWEEN, or LIKE.

Also avoid duplicate indexes. Check existing indexes before creating new ones:

SHOW INDEX FROM your_table;

If you have an index on (col1, col2, col3), you don't need separate indexes on (col1) or (col1, col2) because MySQL can use the leftmost prefix of a composite index.

Mistake 4: Ignoring Query Patterns and Cardinality

Creating indexes without considering how often different queries run or how selective the indexed columns actually are leads to wasted resources.

The Wrong Approach

Indexing columns that have very few distinct values (low cardinality) like boolean flags or status fields with only two or three possible values.

The Correct Approach

Check column cardinality before indexing:

SELECT 
  COUNT(DISTINCT status) as distinct_values,
  COUNT(*) as total_rows,
  COUNT(DISTINCT status) / COUNT(*) as selectivity
FROM your_table;

A selectivity ratio close to 1.0 means the column is highly selective and a good candidate for indexing. A ratio close to 0.0 means the column has few distinct values and an index may not help much.

For low-cardinality columns that appear in many queries, consider composite indexes that combine them with high-cardinality columns, rather than indexing them alone.

Mistake 5: Fetching More Data Than Needed

Queries are often slow not because of poor indexing but because they retrieve far more data than the application actually uses.

The Wrong Approach

Using SELECT * everywhere and fetching thousands of rows when only ten are displayed, or loading all columns when only two are needed.

The Correct Approach

Be explicit about what you need:

-- Instead of:
SELECT * FROM posts ORDER BY created_at DESC;

-- Use:
SELECT id, title, created_at 
FROM posts 
ORDER BY created_at DESC 
LIMIT 20;

Fetching only required columns reduces I/O, memory usage, and network transfer. Always use LIMIT when you don't need all matching rows.

For pagination, avoid OFFSET with large values:

-- Slow for large offsets:
SELECT id, title FROM posts ORDER BY id LIMIT 1000, 20;

-- Better - use keyset pagination:
SELECT id, title FROM posts 
WHERE id > 12000 
ORDER BY id 
LIMIT 20;

Mistake 6: Not Monitoring Query Frequency and Total Impact

Optimizing the single slowest query in your log may not have much impact if it runs once per day, while a moderately slow query that runs thousands of times per minute is the real bottleneck.

The Wrong Approach

Focusing only on the queries with the highest individual execution time without considering how often they run.

The Correct Approach

Use mysqldumpslow to sort by total time (execution time multiplied by count):

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

The -s t flag sorts by total time spent, showing which queries consume the most database resources overall. These are your highest-impact optimization targets.

For real-time monitoring, query the performance schema:

SELECT 
  DIGEST_TEXT,
  COUNT_STAR as exec_count,
  AVG_TIMER_WAIT/1000000000 as avg_ms,
  SUM_TIMER_WAIT/1000000000 as total_ms
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;

Mistake 7: Overlooking Configuration and Hardware Limits

Sometimes queries are slow because MySQL itself is under-resourced or poorly configured, not because the queries are badly written.

The Wrong Approach

Endlessly tweaking queries and indexes while MySQL runs with default configuration on inadequate hardware.

The Correct Approach

Check key MySQL configuration variables:

SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
SHOW VARIABLES LIKE 'max_connections';
SHOW VARIABLES LIKE 'query_cache_size';

The InnoDB buffer pool should be set to 70-80% of available RAM on a dedicated database server. For shared hosting environments, allocate what you can while leaving room for other processes.

Check if you're hitting connection limits:

SHOW STATUS LIKE 'Max_used_connections';
SHOW VARIABLES LIKE 'max_connections';

If Max_used_connections is close to max_connections, you may need to increase the limit or investigate connection pooling.

Monitor disk I/O wait and memory usage at the system level:

iostat -x 1
vmstat 1

High I/O wait percentages indicate storage bottlenecks that no amount of query optimization will fix.

Mistake 8: Testing Queries in Isolation

A query that performs well when run alone may still cause problems under real-world concurrent load.

The Wrong Approach

Optimizing queries by running them manually in a SQL client without considering lock contention, concurrent connections, or cache effects.

The Correct Approach

Test under realistic load conditions. Use a load testing tool or check production behavior:

SHOW ENGINE INNODB STATUS;

Look for lock waits and deadlocks in the output. If queries lock many rows or tables, consider:

  • Reducing transaction scope
  • Using more selective queries that lock fewer rows
  • Changing isolation levels if appropriate for your application
  • Breaking large operations into smaller chunks

For write-heavy workloads, ensure queries use indexes properly to minimize lock ranges. A full table scan in an UPDATE or DELETE statement can lock the entire table.

Mistake 9: Forgetting to Analyze Tables After Bulk Changes

MySQL's query optimizer relies on table statistics to choose efficient execution plans. Outdated statistics lead to poor query plans even when proper indexes exist.

The Wrong Approach

Adding indexes or making bulk data changes and expecting immediate performance improvements without updating table statistics.

The Correct Approach

Run ANALYZE TABLE after bulk inserts, updates, or deletes, and after creating or modifying indexes:

ANALYZE TABLE your_table;

For InnoDB tables, this updates index cardinality statistics that the optimizer uses. For MyISAM, it also sorts and stores key distribution information.

Schedule regular analysis for tables with frequent changes:

-- Analyze all tables in a database
ANALYZE TABLE database_name.*;

On very large tables, note that ANALYZE TABLE can take significant time and may briefly lock the table, so plan accordingly.

Mistake 10: Ignoring Application-Level Caching

Not every performance problem needs to be solved at the database level. Over-querying the database when data rarely changes wastes resources.

The Wrong Approach

Running the same expensive query hundreds of times per minute to fetch data that changes once per hour.

The Correct Approach

Implement query result caching at the application level using Redis, Memcached, or application-level cache:

// Example pseudo-code
$cacheKey = 'top_posts_' . date('YmdH');
$posts = cache->get($cacheKey);

if (!$posts) {
    $posts = database->query('SELECT ...');
    cache->set($cacheKey, $posts, 3600); // Cache for 1 hour
}

For WordPress hosting, use object caching with Redis or Memcached to reduce database load. For custom applications, cache query results with appropriate TTL values based on how often the data changes.

Database query caching (MySQL's built-in query cache) has been deprecated and removed in recent versions, so application-level caching is the modern approach.

Conclusion

Most slow query problems stem from systematic mistakes in the troubleshooting process rather than complex database issues. Enable the slow query log first, use execution plans to understand query behavior before making changes, create indexes strategically rather than randomly, and always verify changes under realistic load. Remember that application-level caching and proper MySQL configuration are often more impactful than micro-optimizing individual queries. Approach performance tuning methodically, measure the impact of each change, and focus on the queries that consume the most total database time rather than chasing the single slowest query in isolation.

FAQ

How long should I set long_query_time?

Start with 2 seconds for most web applications. If your slow query log fills too quickly with queries you consider acceptable, increase it. For APIs with strict SLA requirements, you might use 0.5 or 1 second. The goal is to capture queries that actually impact user experience.

Should I add indexes to every column in my WHERE clause?

No. Add composite indexes that cover multiple columns used together in queries, ordered by selectivity. Too many indexes slow down writes and consume disk space. Focus on indexes that serve your most frequent or most expensive queries.

Why is my query still slow after adding an index?

Check the execution plan with EXPLAIN to verify MySQL is actually using your index. Common reasons indexes aren't used include: wrong column order in composite indexes, type mismatches in WHERE clauses, functions applied to indexed columns, or the optimizer determining a full table scan is more efficient for small tables.

How do I know if I need more RAM for MySQL?

Check the buffer pool hit ratio. If MySQL constantly reads from disk rather than serving data from memory, increasing innodb_buffer_pool_size helps. Monitor disk I/O metrics and look for high I/O wait times.

Can I remove old indexes safely?

Yes, but verify they aren't used first. Log queries for a representative time period, analyze execution plans, and check the performance schema for index usage statistics. Drop indexes that are never used, but keep the primary key and foreign key indexes.