Distributed Systems & Enterprise ResiliencePlaybook3 min readUpdated September 2026

Reading a Query Plan Before You Add Another Index

A slow-query alert tells you something is slow. It doesn't tell you why, and adding an index without knowing why is how tables end up with a dozen overlapping indexes that all quietly tax every write. Reading the query plan first turns index decisions into something you can defend, not something you guessed at.

How do you read EXPLAIN ANALYZE before adding an index?

Run the actual slow query through EXPLAIN ANALYZE before touching the schema. The output tells you whether the planner is doing a sequential scan across the whole table, whether its row estimate is wildly off from what it actually returned, and where the time is genuinely going. An index only helps if the plan shows a scan pattern an index would actually change.

A wildly wrong row estimate is often a sign the table's statistics are stale, not that an index is missing. Refreshing statistics is a smaller fix than adding an index, and worth ruling out first.

Pay attention to the difference between the planner's estimated rows and the actual rows returned, shown side by side in the output. A close match means the planner had good information to work with; a wide gap means whatever conclusion it reached about the best plan is built on a bad guess, and fixing the estimate can matter more than any index you'd add on top of it.

A worked example: turning a sequential scan into an index scan

Say a query filters on a status column and then sorts by created_at, and the plan shows a sequential scan followed by an explicit sort step. A composite index on status and created_at, in that column order, lets the planner satisfy both the filter and the sort from the index directly, and the plan afterward should show an index scan with no separate sort step.

If the sort step is still there after adding the index, the column order or the index type is wrong, not the idea. Check the plan again after the change instead of assuming the index did what you intended.

The index that made writes slower

Every index the table has gets updated on every insert and update that touches its columns, so an index that speeds up one report query has a cost on every write to that table, whether or not that write ever touches the report.

A partial index, scoped to only the rows a query actually filters for, or a covering index that includes the columns a query selects so it never has to touch the table's main storage at all, often gets you the read speedup with a smaller write-side cost than a blanket index across the whole table.

Should an index advisor decide which indexes to add?

Tools that watch real query patterns and surface candidate indexes are useful for finding what you'd otherwise miss, but they don't know your write volume or which of your existing indexes already overlap the one they're suggesting.

Treat their output as a shortlist to check against EXPLAIN ANALYZE and your existing index list, not as something to apply automatically in production. A candidate that overlaps an existing index by its leading column usually isn't worth adding.

These tools are strongest at surfacing queries you didn't know were slow in the first place, because they watch everything running against the database rather than only the queries someone happened to complain about. Run them periodically even when nothing is currently on fire.

The mistake: indexing the column in isolation from the query

An index on created_at alone doesn't help the query above nearly as much as the composite index does, because the planner still has to scan every matching status row before it can apply the sort benefit.

Column order in a composite index matters: put the columns used in equality filters first and the column used for sorting or range filtering last, matching the order the query actually needs, not the order the columns happen to appear in the table definition.

Check these before you create the index:

  • Put equality-filter columns first in a composite index and the sort or range column last, matching how the query is written.
  • Look for an existing index with the same leading column that already makes the new one redundant.
  • Consider a partial index scoped to the rows the query filters for, since it costs less on every write.
  • Consider a covering index that includes the selected columns, so the query avoids the extra table lookup.
  • After creating it, confirm with EXPLAIN ANALYZE that the planner actually uses the new index.

Watching for bloat on the indexes you keep

An index on a column that gets updated frequently accumulates dead entries as rows change, the same way a table does, and its size can grow well past what the data it covers would suggest. A bloated index still works, but it's slower to scan and slower to maintain than a freshly built one covering the same columns.

Check index size against row count periodically for your highest-churn tables, and rebuild an index that's grown disproportionately rather than assuming it's still the same efficient structure you created.

Executive Capability Standard

What Good Looks Like

Good indexing discipline means every index on a table can be traced to a specific query plan that needed it, and nobody adds one without checking EXPLAIN ANALYZE first.

Building The Capability (5-Stage Skill Ladder)

1. Learn:Learn to read EXPLAIN ANALYZE output well enough to tell a sequential scan from an index scan and to spot a row-estimate mismatch.
2. Do Manually:Run your slowest reported queries through EXPLAIN ANALYZE by hand and list which ones a composite or covering index would actually change.
3. Delegate:Give a database-focused engineer ownership of reviewing new index proposals against the table's existing index list before they ship.
4. Automate:Set up an index-suggestion tool that watches real query patterns, and route its output into the same review step rather than applying it directly.
5. Buy:Bring in a database performance consultant for a one-time review if the table's index list has grown past what anyone on the team can explain.

How to Get Started

Frequently Asked Questions

How many indexes are too many on one table?

There's no fixed number; the real question is whether each index earns its write-side cost. Check for overlap, such as two indexes that both start with the same leading column, before adding a new one, and drop indexes that EXPLAIN ANALYZE shows the planner never actually uses.

Do automated indexing tools work safely on production?

Most only suggest candidates rather than applying changes directly, which is the safer mode to run them in. Treat their suggestions as a shortlist to validate with EXPLAIN ANALYZE against your real query patterns before creating anything, since they don't know your write volume or your existing overlapping indexes.

What's a covering index and when does it help?

A covering index includes every column a query selects, not just the ones it filters or sorts on, so the planner can answer the query entirely from the index without touching the table's main storage. It helps most on read-heavy queries against wide tables where that extra lookup step is the bottleneck.

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