Reading a Query Plan Well Enough to Know Which Index You Actually Need
To find the index a slow query actually needs, read its EXPLAIN ANALYZE plan before adding anything, because an index added blindly often does not help and adds write overhead to every future insert and update on that table. Reading the real plan turns index tuning from guesswork into a targeted fix.
Here's how to read one well enough to know the difference.
Run EXPLAIN ANALYZE, Not Just EXPLAIN
EXPLAIN shows you the planner's estimated plan; EXPLAIN ANALYZE actually runs the query and shows you what really happened, including real row counts and real timing per step. The gap between the planner's estimate and reality is itself diagnostic: a large gap often means your table statistics are stale, which can cause the planner to pick a bad plan even when a good index already exists. Always read the analyzed version before deciding a query needs a new index at all.
How do you find the sequential scan that is the real problem?
A sequential scan, reading every row in a table, isn't automatically bad; on a small table it's often faster than the overhead of using an index at all. The sequential scan worth fixing is the one on a large table, filtering down to a small fraction of rows, taking a meaningful share of the query's total time. Look at the actual row counts in the plan: a sequential scan reading a million rows to return twenty is a strong signal an index on the filtered column would help. A sequential scan reading a million rows to return eight hundred thousand often isn't, since an index wouldn't meaningfully narrow that down anyway.
How do you match an index to the query, not the table?
An index on a single column helps a query that filters on exactly that column, but many real queries filter on a combination of columns, or filter on one column while sorting by another. A composite index, covering the columns in the order your query actually uses them, often outperforms two separate single-column indexes for a query that needs both. Get the column order in a composite index wrong, and the index may not get used at all for a filter that doesn't lead with its first column.
The Cost Side Nobody Checks Before Adding One
Every index speeds up the specific reads it's built for and slows down every write to that table, since the database has to update the index alongside the row itself. A table with heavy write traffic and a dozen indexes, several unused, pays that cost on every insert and update without getting anything back for the unused ones. Periodically check which indexes are actually being used by your query planner, most databases expose this, and drop the ones that aren't earning their write overhead.
A Worked Example
Say a report page that filters orders by customer and sorts by date takes four seconds to load. EXPLAIN ANALYZE shows a sequential scan reading two million order rows to return the forty that belong to one customer, with almost the entire four seconds spent in that scan. A composite index on customer ID and order date, in that order, turns that same query into an index scan reading roughly forty rows directly, and the report page drops to double-digit milliseconds. The fix wasn't guessed, it came directly from reading which step in the plan actually consumed the time.
Letting an Advisory Tool Suggest Indexes, Then Checking Its Work
Several databases and third-party tools can suggest indexes automatically based on observed query patterns, and these suggestions are a reasonable starting point, especially on a schema too large to review by hand. Treat the suggestion as a hypothesis, not a final answer: apply it in a staging environment, run EXPLAIN ANALYZE on the actual queries it's meant to help, and confirm the planner is choosing to use it before rolling it out to production. An automated suggestion applied blindly carries the same write-overhead risk as a manually guessed index, since the tool doesn't know your actual write volume or how many other indexes are already competing for the same benefit.
A short checklist before you add any index:
- Run EXPLAIN ANALYZE rather than plain EXPLAIN, and compare the planner's estimates against the real row counts and timing.
- Fix only sequential scans on large tables that filter down to a small fraction of rows and take a meaningful share of query time.
- Build composite indexes with columns in the order the query actually filters and sorts, instead of stacking single-column indexes.
- Periodically drop indexes that are never used, since each one slows every write to the table.
- Test any advisory tool suggestion in staging with EXPLAIN ANALYZE before applying it to production.
What Good Looks Like
Good indexing means every index in use is traceable to a real query plan that shows it being used, and slow queries get diagnosed from EXPLAIN ANALYZE output before any index gets added.
Building The Capability (5-Stage Skill Ladder)
How to Get Started
Frequently Asked Questions
Why would a query still use a sequential scan even after we added an index?
Usually because of stale table statistics, a filter that does not match the index's leading column, or a table small enough that a sequential scan is faster. Stale statistics make the planner underestimate how selective the index would be. Run EXPLAIN ANALYZE after adding the index to confirm it is actually being used.
How many indexes are too many on one table?
There's no fixed number; it depends on your write volume and how many of the indexes are actually used. A read-heavy table with low write volume can support more indexes without a noticeable cost. A high-write table should carry only the indexes that measurably support real queries, checked periodically rather than assumed to still be needed.
Should we just add an index to every column that shows up in a WHERE clause?
No. That approach adds write overhead across the board while missing the composite indexes that would actually help queries filtering on more than one column together. Base index decisions on actual query plans for your slowest, most frequent queries, not a blanket rule applied to every column mentioned in a filter.
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
Reading a Query Plan Before You Add an Index
Adding an index without checking the query plan can leave the planner ignoring it entirely while every write pays the maintenance cost. A safer workflow.
Reading a Query Plan Well Enough to Fix It Yourself
A practical guide to reading EXPLAIN ANALYZE output, spotting the specific signs of a missing or unused index, and fixing the query plan that's actually slow.
Why Is This Query Slow? A Query Plan Reading Guide
A Q&A guide to reading EXPLAIN ANALYZE output, choosing index types, and telling a genuinely missing index from a redundant one nobody's used.
Reading a Query Plan to Find the Index You're Actually Missing
A worked walkthrough of reading a Postgres query plan to find exactly which index is missing, instead of guessing which columns to index.
Reading a Postgres Query Plan Before You Add an Index
How to read an EXPLAIN ANALYZE output to find out whether a slow query actually needs an index, and the indexing mistakes that slow queries down.
Read the Query Plan Before You Add Another Index
How to use EXPLAIN ANALYZE to find real bottlenecks, why every index has a write cost, and when the fix is a query rewrite instead of an index.