• /
  • /

MySQL Performance Optimization Pitfalls: 10 Common Mistakes and How to Avoid Them

MAR 11, 2025 • WRITTEN BY ROMAN AGABEKOV
Optimizing MySQL often starts with some basic adjustments – maybe you tweak a setting or add an index and see a quick boost. But those simple fixes rarely hold up long-term, like when your database gets more traffic or your data grows, because they don’t tackle the deeper issues at hand.

The traditional approach leaves gaps that turn into real problems later. That’s why you’ve got to pay attention to the bigger mistakes, like running on default settings or letting maintenance slide. Those are the things that’ll slow you down, frustrate users, and have you in a perpetual scramble to catch up.

Before exploring advanced tactics, make sure you have covered the basics outlined in our ultimate MySQL performance tuning guide.

Instead of rewriting code manually, let Releem handle it. Learn more about our automated SQL query optimization feature.

Before applying these fixes, understand the core concepts in our MySQL 8 performance tuning.

1. Writing Inefficient SQL Statements

A bad query can do serious damage. Take a table with 10 million rows – if a query is searching for one record without an index, it scans every single row. That is not only slow, but it can also push CPU usage to 90% and block other queries. A single unoptimized SELECT can turn a 50ms operation into a 5-second slog, dragging the whole system down with it.

Using EXPLAIN and ANALYZE to Detect and Fix Queries

MySQL has two key tools to help you understand and fix your problem queries. Start with EXPLAIN. It lays out MySQL’s plan for running your query. It’s like a blueprint for how the server will fetch your data.
EXPLAIN SELECT name FROM customers WHERE email = 'john@example.com';
You’ll get a table with columns like id, select_type, table, type, key, and rows. Focus on type and rows. If type says “ALL,” MySQL’s doing a full table scan, which means it’s reading every row with no shortcuts. The rows column estimates how many it’ll check, so on a 10-million-row table, that could very well be 10 million. You might also see key listed as NULL, meaning no index is helping.

To fix this, you can then add an index on the column you’re filtering:
CREATE INDEX idx_email ON customers(email);
EXPLAIN shows the optimizer's plan. In MySQL 8.0.18 and later, EXPLAIN ANALYZE executes a supported query and reports actual execution details. Consider the query's workload and execution impact before running it.

Common Query Mistakes to Avoid

Certain query habits can tank performance if you’re not careful. They’re easy to fall into but just as easy to fix once you know what to look for:

  • SELECT *: Using SELECT * pulls every column in the table, even if you only need a couple. This wastes bandwidth and memory. Say you’ve got a table with 20 columns, but you just want to look at the name and email columns. Use SELECT name, email to target those columns and avoid querying the rest.

  • Unindexed JOINs: Joining without indexes on the join fields makes MySQL scan every row – like 10,000 rows in one table against 100,000 in another, hitting a billion comparisons. The ON clause (e.g., ON orders.customer_id = customers.id) specifies which columns to match. Index those columns to avoid a full scan.

  • Missing Filters: If you don’t use WHERE or LIMIT to filter your queries, MySQL reads every row in the table, even if you don’t need them all. This is brutal for large tables. But when you add filters like SELECT name FROM customers WHERE active = 1 LIMIT 10, then you are reducing the execution time because you are requesting that this query only return 10 active customers.

2. Over-relying on Default Configurations

Out of the box, MySQL is tuned for compatibility and safety, not speed. For example, the innodb_buffer_pool_size starts at 128MB – on a 16GB server, that’s less than 1% of your RAM, so you’ll be stuck with constant disk reads. Defaults may work for small setups but tend to cap out pretty fast with real traffic.

Configuration Settings to Review

  • innodb_buffer_pool_size: Allocate a large percentage of available system memory to keep frequently accessed InnoDB data cached. This minimizes disk I/O.

  • max_connections: Scale your connections based on expected traffic and simultaneous users, but monitor overall memory usage to catch resource strain.

  • tmp_table_size: Set to accommodate large temporary datasets in memory. This avoids unnecessary disk swaps during complex queries.

  • Redo log capacity: In MySQL 8.0.30 and later, review innodb_redo_log_capacity for your workload and existing configuration. Earlier versions use different redo log settings.

  • table_open_cache: Match to the number of tables actively used in your environment to reduce repetitive file operations.

  • thread_cache_size: Size to recycle frequently used threads effectively and skip the overhead of constantly creating new ones.

  • innodb_flush_log_at_trx_commit: Choose between strict data safety or faster writes, tailoring to your tolerance for performance versus recovery risk.

  • key_buffer_size: Allocate memory for indexing if using engines that benefit from key caching, particularly in mixed-storage environments.

  • sort_buffer_size: Assign enough memory per connection for complex sorting tasks but avoid overallocation for simpler queries to balance speed with responsible memory use.
For a full breakdown, check our performance parameters list.

Tuning Made Easier with Releem

Doing this by hand means a lot of testing and guessing. Releem looks at your actual workload and suggests precise values. It automates the whole tuning process by monitoring performance metrics, spotting bottlenecks, and suggesting optimizations. You can apply these configuration recommendations automatically or via a cron job or one-click updates, or review them first in the dashboard if you prefer control.
Releem interface for reviewing and applying MySQL configuration recommendations
Read more about Releem’s Tuning Process here.

3. Improper Indexing Strategies

Indexes come with trade-offs. If you add too many, like 10 indexes, on a single table, then every time you run an INSERT or UPDATE, MySQL has to update all of those indexes. That can tack on something like 10 milliseconds per write.

On the flip side, if you don’t have enough indexes, your reads will suffer. A simple SELECT on a table with a million rows might take 20 ms with an index, but without one, this might stretch to 2 seconds or more to complete a full table scan.

How to Find Indexes You Don’t Need

Use SHOW INDEX FROM table_name, which lists all indexes on a table along with the columns they cover. Then, check if your queries actually use them by running a tool like pt-index-usage (from Percona Toolkit) against your slow query log.

A slow query log covers only part of your workload. Before removing an index, review representative queries, constraints and dependencies, and prepare a rollback plan.

Smarter Indexing Options

To get more out of your indexes, consider techniques like covering indexes, which pack all the columns a query needs into the index (CREATE INDEX idx_cover ON example_table(column1, column2)) so MySQL can skip fetching data from the table.

MySQL does not support a WHERE clause in CREATE INDEX to build an index over only matching rows. Choose indexes for the queries and data you actually have, check the execution plan, and weigh their write and storage costs.

And finally, combining multiple columns into a single index (CREATE INDEX idx_multi ON example_table(column1, column2)), letting queries that filter on several fields run faster by narrowing results in one go instead of multiple passes.

Learn more about how to set this up in our guide to creating a MySQL composite index.

4. Mismanaging Connection Handling

Every time an app opens a new connection to MySQL, it takes about 1-2 milliseconds to set up the handshake between the app and the server. If your application is opening up connections constantly, those milliseconds can add up to 1 to 2 seconds of delay.

Plus, MySQL has a limit called max_connections, set to 151 by default, which limits how many connections can be opened at once. If traffic spikes and you hit that cap, new users can’t connect and get hit with a “Too Many Connections” error.

How to Keep Connections Open and Reduce Overhead

Persistent connections reuse what’s already there, which avoids the 1-2 ms delay of opening new ones. Depending on your ecosystem and performance needs, you have a few options:

  • In PHP, enabling mysqli.persistent allows MySQL connections to persist across requests.

  • In Java, you can use HikariCP to maintain a pool of ready-to-use connections, which is ideal for high-load scenarios.

5. Ignoring Database Maintenance

Review table health and optimizer statistics in the context of your workload. Operations such as OPTIMIZE TABLE and ANALYZE TABLE can consume resources or affect availability. Evaluate whether an operation is needed and plan it for the table, engine and environment before scheduling it.

6. Not Monitoring Performance Effectively

If you don’t have proper monitoring mechanisms in place, you’ll miss problems like a query suddenly taking 10 seconds instead of 1 or the buffer pool swapping to disk because it's too small – stuff users will notice before you do.

Make sure you’re tracking the following metrics if you want to spot trouble early:

  • MySQL Latency
  • Throughput
  • Slow Queries Count
  • Aborted Clients
  • CPU Usage
  • RAM Usage
  • SWAP Usage
  • Input/Output Operations per Second (IOPS)

You can find more detailed information about these metrics here.

Monitoring with Releem

Releem’s a solid pick for monitoring because it makes keeping an eye on these metrics easy, not a headache like some older tools. The interface is clean and user-friendly, giving you a straightforward way to visualize CPU load, disk I/O, or slow queries so you can efficiently address problems. You can even check specific time ranges to view how your MySQL server has performed over time.
Releem monitoring dashboard showing database and system performance metrics
Learn more about Releem’s monitoring capabilities here.

7. Failing to Address Hardware Limitations

Weak hardware can choke your database, no matter how well you tune it. Slow CPUs choke on queries, low RAM forces disk I/O, and sluggish disks (think HDDs vs. SSDs) lag on writes. You can’t outrun these issues.
If your hardware is holding you back, you can scale vertically or horizontally. Vertical scaling involves adding more RAM or a faster CPU to your one server. It’s a simple fix, but you’ll eventually hit a ceiling on how much you can upgrade. Or you can go with horizontal scaling by adding extra servers that will split the load. This approach is more work to set up with replication stuff to manage, but it's far more scalable.

8. Overlooking Caching Strategies

MySQL’s built-in query cache sounds nice in theory. It speeds up repeated reads by storing results, but this creates a headache for writes. Changes to a table invalidate cached results for that table. The query cache was removed in MySQL 8.0.

Better Ways to Cache

  • Redis: A super-fast, in-memory caching tool that stores data like query results or sessions in RAM, cutting database hits. It’s flexible, supports complex data types, and scales easily for big applications.

  • Memcached: A straightforward, lightweight caching option that keeps things simple with key-value storage. It’s quick to set up, great for basic caching like page snippets, and works well in smaller setups.

  • ProxySQL: A smart proxy that sits between your app and MySQL, caching query results at that layer. It’s easy to tweak, boosts read performance without app changes, and handles load balancing, too.

  • Releem: tracks cache performance and suggests intelligent cache tuning. It analyzes metrics like hit ratios and query patterns, helping you optimize caching for your specific workload.

9. Choosing a Storage Engine

Picking the right storage engine depends on your workload. InnoDB is your best bet for high-transactional, concurrency-heavy applications because it offers robust support for ACID transactions, row-level locking, and reliable crash discovery. MyISAM is better for read-intensive scenarios like reporting because it delivers faster query performance with a simpler architecture.

10. Misunderstanding COUNT() Query Optimization

One of the most common MySQL performance pitfalls is misunderstanding how COUNT(*) actually works. Many developers assume the query will be fast as long as “there’s an index,” but the truth is more nuanced and depends heavily on the storage engine and index design.

MyISAM vs InnoDB

For a simple SELECT COUNT(*) FROM table_name without a WHERE clause, MyISAM can return the stored row count. InnoDB counts rows visible to the transaction and may scan an index. Filtered queries and joins can have different plans, so use EXPLAIN to inspect the query you need to optimize.

Covering Indexes

The most effective way to speed up COUNT() queries in InnoDB is to use a covering index (index-only scan). When the index contains all the data needed for the query, MySQL can satisfy it directly from the index without accessing the actual table rows.

Create well-chosen secondary indexes on columns you frequently filter by — such as status, user_id, created_at, or category_id. When MySQL can use such a covering index, COUNT() performance improves dramatically.

Understanding the fundamental differences between MyISAM and InnoDB, along with the power of covering indexes, helps eliminate many slow dashboard and reporting queries. Always verify your assumptions by checking the execution plan with EXPLAIN.

Tune the levers that actually matter

MySQL performance degrades quickly when queries scan more rows than they need, configurations are left at defaults, or maintenance is ignored. The fixes are usually straightforward: tune the configuration for your workload, fix the indexes that are forcing expensive scans, and rewrite or remove the queries that do unnecessary work. In most cases, these three are the biggest offenders, and they're the places where you'll see the fastest improvement.

Once those are in place, work through the remaining pitfalls one at a time until the entire database is tuned, not just patched. Stay curious, measure before and after each change, and use automation to handle the repetitive analysis so you can focus on the decisions that need your judgment.

Article by

  • Founder & CEO
    Roman Agabekov has 17 years of experience managing and optimizing MySQL and MariaDB in high-load environments. He founded Releem to automate routine database management tasks like performance monitoring, tuning, and query optimization. His articles share practical insights to help others maintain and improve their databases.