Engineering Leadership & Technical HiringPlaybook3 min readUpdated September 2026

Why Is This Query Slow? A Query Plan Reading Guide

"Add an index" is the reflexive answer to a slow query, and it's wrong often enough to be worth slowing down for. An index that isn't selective enough won't get used by the planner regardless of whether it exists, and a database with too many redundant indexes pays a real write-amplification cost for indexes that never actually speed up a read.

This walks through the questions worth answering before adding, or removing, an index.

How Do I Actually Read an EXPLAIN ANALYZE Output?

Start from the bottom of the plan tree, that's where execution actually begins, and work up. Look for a sequential scan on a table with a large row count where you expected an index scan; that's the clearest signal something's missing. Compare the planner's estimated row count against the actual row count at each step; a large gap between estimate and actual usually means outdated table statistics, which is a different fix, running an analyze, not a missing index.

When Does the Planner Choose Not to Use an Index That Exists?

If a query's filter matches a large fraction of the table's rows, a sequential scan can genuinely be cheaper than an index scan, since jumping between the index and the table's actual rows for a large fraction of the table costs more than reading the table straight through. This is the most common reason engineers see a sequential scan and assume the index is broken, when the planner is actually making a reasonable cost-based choice for a low-selectivity filter. Check the filter's selectivity, roughly what fraction of rows match, before concluding the index needs fixing.

Which Index Type Actually Fits This Query?

A standard B-tree index handles equality and range queries on ordered data well and covers most cases by default. A partial index, one that only covers rows matching a condition like `WHERE status = 'active'`, is worth it when queries consistently filter to a small, well-defined subset of a much larger table. A covering index, one that includes every column a query needs so the planner never has to touch the table itself, is worth the extra storage for a genuinely hot, narrow query path. Picking B-tree by default and reaching for the others only when a specific query pattern justifies it keeps index maintenance manageable.

How Do I Tell a Redundant Index From One That's Just Rarely Used?

Most databases expose index usage statistics, a count of how many times an index has actually been used to satisfy a query since the counter was last reset. An index with a genuinely zero count over a full, representative traffic cycle, not just a quiet weekend, is a real candidate for removal; one that's used rarely but for a critical, if infrequent, query, a monthly billing job, a rare admin action, should stay even though its usage count looks low. Review index usage quarterly, not reactively only when someone notices write performance has degraded.

What's the Actual Cost of an Index Nobody Uses?

Every index has to be updated on every write to the table it covers, so an unused index isn't free, it's a tax on every insert, update, and delete that provides no corresponding read benefit. On a high-write table, several unused indexes can measurably slow down write throughput, and it's a cost that's easy to overlook because it shows up as gradually degraded write latency rather than an obvious, attributable spike tied to a specific change.

Can Automated Index Advisors Replace This Process?

Automated tools that suggest indexes based on observed query patterns are useful as a starting point, especially for surfacing candidates on a table nobody's looked closely at in a while, but they still need a human to confirm the suggestion against actual query plans and to weigh the write-amplification tradeoff for that specific table's traffic pattern. Treat an automated suggestion the same way you'd treat a junior engineer's first pass, a reasonable starting point that still needs review, not something to apply automatically to a production table without checking.

Run through these checks before adding or dropping an index:

  • Read the EXPLAIN ANALYZE output from the bottom up, looking for sequential scans on large tables where you expected an index scan.
  • Confirm the filter is selective enough that the planner would actually choose the index.
  • Pick the index type that fits the query, such as a B-tree for equality and ranges or a partial index for a consistent filter.
  • Check usage statistics over a full, representative traffic cycle before dropping an index that looks unused.
  • Confirm any automated index suggestion against your actual query patterns, and watch for bloat on indexes that are used.

Index Bloat Is a Separate Problem From Missing or Redundant Indexes

An index that's genuinely useful and actively used can still degrade over time through bloat, dead entries from updated or deleted rows accumulating faster than routine maintenance cleans them up, which slows down both the reads it's supposed to speed up and the writes that maintain it. This is a maintenance problem, not a design problem, and it needs its own periodic check, rebuilding or reindexing on a schedule, separate from the question of whether the index should exist in the first place.

Executive Capability Standard

What Good Looks Like

Every index you add should trace back to a real, slow query plan, not a guess about what might help.

Building The Capability (5-Stage Skill Ladder)

1. Learn:Read EXPLAIN ANALYZE output on your slowest queries before adding a single new index.
2. Do Manually:Manually review your index list quarterly for ones nothing has used since the last review.
3. Delegate:Give one engineer ownership of index changes so they go through the same review as a schema change would.
4. Automate:Alert on sequential scans against your largest tables so a missing index surfaces before customers notice slow pages.
5. Buy:Consider a database performance monitoring tool with automated index advisories once manual plan review can't keep pace with query volume.

How to Get Started

Frequently Asked Questions

How often should we review our indexes for ones that aren't being used?

Quarterly is a reasonable cadence for most teams, checked against a full traffic cycle rather than a quiet period, so you don't accidentally drop an index that only a monthly job relies on.

Should every foreign key column automatically get an index?

Usually yes for the child side of the relationship, since queries and joins frequently filter or join on foreign key columns, and many databases don't create this index automatically just because a foreign key constraint exists.

Is it safe to add an index on a large, actively written table without downtime?

Most modern databases support building an index concurrently or online without locking the table for the duration, though it takes longer than a blocking build and briefly increases load while it runs. Check your specific database's syntax for a non-blocking index build before running one against a large production table.

About the numbers

This guide doesn't quote a sourced benchmark. Figures in it are estimates or general guidance, so check them against your own numbers.

Related Guides