Skip to main content
Performance

Finding and Fixing Slow Database Queries

MMercy Chepkemoi14 September 20267 min read2829 views

Every digital application—whether it is a bustling e-commerce platform in Nairobi, a fintech banking portal, or a content-heavy news site—eventually hits a performance wall. Users begin complaining about spinning loaders, API requests timeout, and your server CPU utilization spikes into the red.

More often than not, the root cause of these performance bottlenecks is not insufficient hardware or poor cloud configuration; it is slow database queries.

A single unoptimized query executing inside a loop can bring even a robust Virtual Private Server (VPS) to its knees. Database optimization yields the highest performance return on investment (ROI) of any engineering effort. Fixing a single query can drop response times from seconds to milliseconds and instantly relieve pressure on your infrastructure.

This guide provides a systematic, step-by-step approach to finding, analyzing, and fixing slow database queries in production environments like MySQL and PostgreSQL.

Step 1: Identifying Your Slowest Queries

You cannot optimize what you do not measure. Relying on user complaints or guessing which part of your application is lagging is a recipe for wasted time. You need hard data on what your database is spending its time executing.

1. Enable and Inspect the Slow Query Log

Both MySQL and PostgreSQL allow you to log queries that exceed a specific execution threshold.

  • In MySQL: You can enable the slow query log in your configuration file (my.cnf):

    Ini, TOML

    slow_query_log = 1
    slow_query_log_file = /var/log/mysql/slow.log
    long_query_time = 0.5 # Logs any query taking longer than 500ms
    
  • In PostgreSQL: Use the built-in extension pg_stat_statements, which tracks execution statistics for all executed queries:

    SQL

    -- Enable the extension in your database
    CREATE EXTENSION pg_stat_statements;
    
    -- Find the top 10 most time-consuming queries
    SELECT 
        round(total_exec_time::numeric, 2) as total_time_ms,
        calls,
        round(mean_exec_time::numeric, 2) as avg_time_ms,
        query
    FROM pg_stat_statements
    ORDER BY total_exec_time DESC
    LIMIT 10;
    

Focus your initial optimization efforts on queries consuming more than 5% of total database time or those with high execution frequencies.

Step 2: Uncovering the Why Using Execution Plans

Once you have isolated a slow query, do not rewrite it blindly. You need to understand why the database engine is struggling with it. This is done using execution plans via the EXPLAIN (and EXPLAIN ANALYZE for PostgreSQL) command.

Prefix your query with EXPLAIN to see the roadmap the database database engine builds to fetch your data:

SQL

EXPLAIN ANALYZE 
SELECT * FROM orders WHERE customer_id = 48291 AND status = 'pending';

Red Flags to Look For in Execution Plans:

  • Full Table Scans (Seq Scan in Postgres, ALL in MySQL): The database is forced to read every single row in the table from disk to find your data. On a table with millions of rows, this takes seconds instead of microseconds.

  • High Loop Counts: Nested loop joins where an outer query iterates thousands of times to fetch inner rows individually.

  • Using Temporary Files on Disk: Queries that require sorting or hashing datasets larger than the allocated memory work buffer (work_mem), drastically slowing down execution.

Step 3: Common Query Anti-Patterns and How to Fix Them

Most slow queries stem from a few predictable structural anti-patterns. Review your codebase for these common culprits:

1. The SELECT * Trap

Fetching columns you do not need wastes network bandwidth, memory, and prevents the database from leveraging covering indexes.

  • Bad: SELECT * FROM users WHERE status = 'active';

  • Good: SELECT id, username, email FROM users WHERE status = 'active';

2. Wrapping Columns in Functions

If you apply a function to a column inside a WHERE clause, the database can no longer utilize an index on that column because it has to evaluate the function for every single row.

  • Bad: WHERE YEAR(created_at) = 2026

  • Good: WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01'

3. The N+1 Query Problem

An N+1 problem happens when your application fetches a parent list of records (1 query) and then executes a separate query inside a loop for each individual record ($N$ queries).

  • The Fix: Replace loop queries with a single, well-structured JOIN statement or batch-fetch identifiers using an IN (...) clause.

Step 4: Applying Smart Indexing Strategies

Indexes are the single most effective tool for transforming slow queries into instant lookups. Think of an index like the index at the back of a textbook: it allows the database to jump straight to the correct data without reading every page.

  • Index Columns You Filter and Join Against: If a column frequently appears in your WHERE, ORDER BY, or JOIN ON clauses, it is a prime candidate for an index.

  • Build Composite Indexes: If you query multiple columns together (e.g., filtering by status and sorting by created_at), build a multi-column composite index:

    SQL

    CREATE INDEX idx_orders_status_created ON orders(status, created_at DESC);
    
  • Avoid Over-Indexing: Every index you add speeds up reads but slows down writes (INSERT, UPDATE, DELETE) because the index tree must be updated. Index only what serves critical user queries.

Step 5: Caching Expensive Results

Not every query needs to hit the database on every single page view. Implement a multi-level caching strategy for data that changes infrequently:

  • Application-Level Memory Cache: Store small reference datasets or user session states in memory maps or local application caches.

  • Distributed Caching (Redis/Memcached): Offload heavy analytical queries or expensive dashboard aggregations into a Redis cache with a defined Time-To-Live (TTL).

  • Database Materialized Views: For complex reporting queries that aggregate millions of rows, use materialized views and refresh them periodically via background cron jobs rather than computing them live on every request.

Conclusion

Optimizing slow database queries is an ongoing engineering discipline, not a one-time chore. By proactively monitoring slow query logs, reading execution plans, eliminating anti-patterns, and applying targeted indexes, you can protect your database from performance degradation as your user base scales.

Frequently Asked Questions (FAQs)

1. How do I know if my slow query needs an index or more server RAM?

If an execution plan shows a Full Table Scan (Seq Scan) on a large table, it is a structural query and indexing problem. If your query uses indexes correctly but your database server's cache hit ratio drops below 90% (meaning it constantly reads from disk instead of RAM), your server requires more memory or a larger buffer pool allocation.

2. Can having too many indexes slow down my database?

Yes. While indexes drastically accelerate read operations (SELECT), they impose a performance penalty on write operations (INSERT, UPDATE, DELETE) because the database engine has to update every associated index tree whenever data changes. Only create indexes that are backed by actual query performance metrics.

3. What is a covering index?

A covering index is an index that includes all the columns requested by a specific query (using features like PostgreSQL's INCLUDE clause). Because the database can find all required data directly inside the index structure itself, it never needs to perform a secondary lookup against the actual table rows.

4. Why is my query running slow in production when it was fast locally?

Local development environments typically run on tiny datasets (dozens or hundreds of rows), meaning a full table scan finishes instantly. Production environments handle millions of rows, exposing the mathematical reality of unoptimized queries. Always test queries with production-scale data volumes during staging.

5. Should I use stored procedures to speed up queries?

Stored procedures execute logic directly on the database server, reducing network round-trips between your application server and database. While they can help with complex, multi-step batch processing operations, they do not fix underlying structural flaws like missing indexes or bad join logic.

Frequently asked questions

How do I find which queries are slow on my server?
Enable the slow query log on the database server, set a threshold such as one second, and let it run under normal traffic. The log names the queries and the tables involved, which is the diagnosis half done.
Will adding RAM fix a slow database?
Sometimes — if the working set no longer fits in memory, more RAM helps enormously. But a missing index or a query scanning a full table stays slow at any memory size. Diagnose before buying hardware.
#database#performance#mysql#vps
M

Written by Mercy Chepkemoi

SteboHost engineering and operations team. We write practical guides on hosting, domains, email and security for African businesses — based on what we see in the field, not marketing copy.

Ready to get started?

Browse our hosting plans or contact our team for help choosing the right one.