{"post":{"slug":"what-is-a-covering-index-2026","title":"What is a covering index? The 2026 guide to index-only scans","excerpt":"A covering index holds every column a query needs, so the database never has to fetch the underlying row. Here is how that shortcut actually works.","category":"code","date":"July 17, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">C</span>overing indexes contain every column a specific query needs, so the database can answer it by reading the index alone and never touch the underlying table row. Normally, an index scan finds a matching entry, then follows a pointer to fetch the actual row for any column not stored in the index. A covering index removes that second step entirely — an index-only scan — one of the more reliable performance wins once a table is large enough for the difference to matter.\n\n## What changed in 2026\n\n- **INCLUDE clause usage kept growing as more developers learned it exists.** It has been available in Postgres since version 11, but plenty of teams still default to widening the index key itself instead, which costs more than necessary.\n- **Visibility map monitoring got first-class dashboards.** Several managed Postgres providers now surface the percentage of a table that is \"all-visible,\" making it easier to see whether index-only scans are actually happening versus falling back to a heap fetch.\n- **MySQL's InnoDB covering-index behavior became easier to verify** with clearer EXPLAIN output showing \"Using index\" versus \"Using index condition,\" reducing guesswork about whether a query is actually covered.\n\n## What makes a scan index-only\n\nCompare two nearly identical queries against `orders(user_id, status, total, created_at)` with an index on `(user_id, status)`:\n\n```sql\n-- NOT covered: total is not in the index, so Postgres must fetch the row\nSELECT total FROM orders WHERE user_id = 42 AND status = 'paid';\n\n-- Covered: every selected and filtered column is in the index\nSELECT user_id, status FROM orders WHERE user_id = 42 AND status = 'paid';\n```\n\nThe second query can be answered from the index alone. The first needs one more step — a heap fetch — for every matching row, because `total` was never part of the index.\n\n## Building a covering index\n\nThe INCLUDE clause adds columns to the index for exactly this purpose, without making them part of the sortable key:\n\n```sql\nCREATE INDEX idx_orders_user_status\n  ON orders (user_id, status)\n  INCLUDE (total, created_at);\n\n-- Now this query is fully covered:\nSELECT total, created_at FROM orders\nWHERE user_id = 42 AND status = 'paid';\n```\n\nINCLUDE columns live in the index's leaf pages but are not used for sorting or range comparisons — they exist purely so a covered query never needs to visit the table. That makes them cheaper than extending the key itself, since a wider key means a larger tree and slower comparisons at every level.\n\n## The catch: visibility and write cost\n\n| Requirement | What happens if it is missing |\n| --- | --- |\n| All selected columns present in the index (key or INCLUDE) | Falls back to a normal index scan with a heap fetch per row |\n| Postgres visibility map marks the relevant pages all-visible | Falls back to a heap fetch even though the index technically covers the query |\n| Table is not extremely write-heavy on the included columns | Every UPDATE to an included column still has to update the index, same as any indexed column |\n\nThe visibility map point catches people off guard: Postgres uses MVCC, so a row's visibility cannot always be determined from the index alone — it may need to check the table, unless the visibility map already marked that page all-visible. A table with heavy recent writes and infrequent VACUUM can have a covering index that still triggers heap fetches simply because the visibility map has not caught up.\n\n## When to reach for one\n\nCovering indexes pay off most for narrow, frequently run queries — an API endpoint hit thousands of times a minute, a hot path in a dashboard, a lookup inside a larger transaction. They are a form of [query optimization](/blog/what-is-query-optimization-2026) that trades write cost and index size for read speed on exactly the queries you choose to cover, a better trade than blindly including every column in every index. A [materialized view](/blog/what-is-a-materialized-view-2026) is a related but different tool for a similar goal — pre-computing an entire result rather than avoiding one row fetch per match.\n\n## FAQ\n\n**Is a covering index the same thing as a composite index?**\nRelated but distinct. A [composite index](/blog/what-is-a-composite-index-2026) spans multiple columns for filtering and sorting; a covering index specifically contains every column a query needs so it never has to fetch the row. A composite index can happen to be covering, or not, depending on what a given query selects.\n\n**Do I need INCLUDE, or can I just add the columns to the index key?**\nINCLUDE is usually better when those columns are not used for filtering or sorting — it keeps the sortable key narrower and the tree structure smaller, which speeds up every lookup, not just the covered ones.\n\n**How do I check whether a query is actually getting an index-only scan?**\nRun EXPLAIN with ANALYZE and BUFFERS and look for \"Index Only Scan,\" plus the \"Heap Fetches\" count — a high count means the visibility map is not keeping up despite the index technically covering the query.\n\n## Where to go next\n\n- [What is a composite index in 2026](/blog/what-is-a-composite-index-2026)\n- [What is query optimization in 2026](/blog/what-is-query-optimization-2026)\n- [What is a materialized view in 2026](/blog/what-is-a-materialized-view-2026)","hue":240,"metaTitle":"What Is a Covering Index? 2026 Guide","metaDescription":"What is a covering index in 2026? How index-only scans work, the INCLUDE clause, and why the visibility map decides whether the shortcut applies.","keywords":["what is a covering index 2026","covering index explained","index only scan postgres","include clause index sql","covering index vs composite index","postgres visibility map index","index only scan requirements","covering index example sql","when to use covering index"],"tldr":["**A covering index contains every column a query touches** — the database answers entirely from the index, skipping the table itself.","**This is called an index-only scan**, and it can be several times faster than an ordinary index scan that still fetches each row.","**The INCLUDE clause adds columns without widening the sortable key** — cheaper than putting every column into the index key itself.","**It is not automatic even with the right columns** — Postgres also needs its visibility map current, which means regular VACUUM matters.","**Skip:** including columns in the index just in case — every included column adds write cost and index size for queries that may never ask for it."],"readTime":"4 min read","wordCount":874},"related":[{"slug":"what-is-a-composite-index-2026","title":"What is a composite index? The 2026 guide to column order","excerpt":"A composite index covers more than one column, and the order of those columns changes which queries it can actually help. Here is how to get it right.","category":"code","date":"July 17, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":200},{"slug":"index-only-scans-explained-2026","title":"Index-Only Scans: When the Table Is Never Touched","excerpt":"If every column a query needs is in the index, the database can answer without reading the table at all. That is frequently the difference between a slow query and an instant one.","category":"code","date":"January 4, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":303},{"slug":"what-is-a-stored-procedure-2026","title":"What is a stored procedure? The 2026 guide with tradeoffs","excerpt":"A stored procedure is a named, reusable block of logic that lives inside the database itself. Here is what that buys you in 2026, and what it costs.","category":"code","date":"July 20, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":150}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"what-is-a-covered-call-2026","title":"What Is a Covered Call? 2026 Strategy Guide","excerpt":"A covered call trades away upside for upfront income. Here is how the strategy works in 2026 and when giving up that upside is not worth it.","category":"finance","date":"July 17, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":215},"older":{"slug":"what-is-a-cpu-cache-2026","title":"What Is a CPU Cache in 2026: A Plain-English Guide","excerpt":"A CPU cache is a small pool of very fast memory sitting on the processor itself, built to avoid slow trips to system memory. Here is what it does and why its size affects real performance.","category":"tech","date":"July 17, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":195}}