skip to content

Indexes & Access Paths

How relational engines structure indexes and use them to reach rows: B+tree and hash mechanics, composite and covering indexes, clustered vs heap storage, and the costs indexes impose on writes. Interviewers lean on this area because it separates candidates who type CREATE INDEX from those who can predict whether the engine will actually use it.

part ofRelational database conceptsoverview, primer and where to startread it →
on this pageshow

questions

67 · 14 sections

Describe how a B+tree index is laid out in storage: what does an internal (branch) node contain versus a leaf node, and how does a key lookup use each of them?

level: juniorimportance: must knowfreq 70%
basics
~20 s

A B+tree is a balanced tree of page-sized nodes. Internal nodes store only separator keys and child pointers used to route a search downward. Leaf nodes store every indexed key in sorted order with a pointer to the row. A lookup descends root to leaf.

open as a page

Explain what fanout means for a B+tree index, how it is determined, and why an index over a billion rows is typically only three or four levels deep.

level: middleimportance: must knowfreq 64%
basics
~20 s

Fanout is how many children one node points to, roughly page size divided by entry size — often several hundred. Height is about log-base-fanout of the row count, so with fanout ~500 three levels index ~125 million keys and four levels index billions.

open as a page

What distinguishes a B+tree from a classic B-tree, and why do relational storage engines almost always choose the B+tree variant for table indexes?

level: middleimportance: should knowfreq 42%
basics
~20 s

In a classic B-tree, payload lives in every node; in a B+tree, only leaves carry keys with row pointers and internal nodes hold pure separators. That keeps branch entries tiny, so fanout is higher and the tree shallower, and all data sits in one sorted, linked leaf level.

open as a page

An index is defined on a wide key — say a 60-byte composite of text columns — instead of an 8-byte integer. Structurally, what changes inside the B+tree, and what are the practical consequences?

level: seniorimportance: should knowfreq 44%
basics
~20 s

Wider entries mean fewer per page, so fanout drops several fold. The tree grows taller and, more importantly, the index occupies far more pages, so much less of it fits in cache. Result: more page reads per lookup and more memory pressure.

open as a page

For a point lookup through a four-level B+tree index, how many physical disk reads would you actually expect, and how do you reason about that with an interviewer?

level: seniorimportance: should knowfreq 38%
basics
~20 s

Four levels means four logical page accesses, but the root and upper internal levels are tiny and stay cached, so typically only the leaf access — and then the row fetch — can be physical. Expect roughly zero to two real reads on a warm cache.

open as a page

Why can a B+tree index efficiently return every row whose key falls within a range, and produce rows already in key order for an ORDER BY without a separate sort step? What property of the structure makes that possible?

level: juniorimportance: must knowfreq 70%
basics
~20 s

All entries live in leaf pages, sorted by key, and the leaves are chained to their neighbours in key order. So the engine descends once to the first matching key and then walks the chain until the range ends, reading entries already in order.

open as a page

Walk through what a B+tree index does when a new entry must be placed into a leaf page that is already full. What is a page split, and what does it cost?

level: middleimportance: must knowfreq 65%
basics
~20 s

It allocates a new page, moves roughly half the entries into it, links it into the leaf chain, and pushes a separator key up to the parent. If the parent is also full it splits too, and splitting the root adds a level to the tree.

open as a page

How does inserting keys in ever-increasing order — a sequence-generated id or an insert timestamp — differ inside a B+tree index from inserting randomly distributed keys such as random UUIDs? Consider split behaviour, page density, write I/O and concurrency.

level: seniorimportance: must knowfreq 55%
basics
~20 s

Increasing keys always land at the rightmost leaf, so engines split it lopsidedly (nearly all entries stay), pages fill densely, and writes stay concentrated and sequential. Random keys split pages all over the index, settling near two-thirds density with scattered, cache-hostile writes — but they avoid the rightmost-page contention hot spot.

open as a page

A query filters on a range of one column and sorts by that same column in descending order. How can a B+tree index satisfy that ordering without a separate sort step, what property of the leaf level makes a descending walk possible, and when does the engine sort anyway?

level: middleimportance: should knowfreq 40%
basics
~20 s

An index scan emits entries in key order, so the planner can drop the sort. Descending output comes from walking the leaf chain backwards, which requires backward sibling links (or an equivalent). The engine sorts anyway when the ordering does not match the index, or when a full scan plus sort is cheaper.

open as a page

What happens to a B+tree index page when its entries are deleted? Describe underflow, merging and redistribution, and explain why real implementations often do not merge pages aggressively.

level: seniorimportance: should knowfreq 33%
basics
~20 s

Textbook B+trees merge or redistribute when a page drops below half full, removing a separator from the parent and possibly shrinking the tree. Real engines mostly defer: entries are marked dead, only fully empty pages are recycled, because merging needs multi-page locking and MVCC forbids early removal.

open as a page

How does a hash index locate a row? Walk through what happens from the lookup key to the row pointer, and describe how the index is laid out on disk.

level: juniorimportance: must knowfreq 42%
basics
~20 s

A hash function turns the key into a bucket number. The engine reads that one bucket, compares the stored keys against the search key (different keys can collide), and follows the matching entry's row pointer. Roughly constant cost, equality lookups only.

open as a page

A column has a hash index on it. Explain why that index can serve a lookup for one exact value but cannot serve a range filter such as "created_at greater than a given timestamp", a prefix match such as "name starts with A", or a request for rows sorted by that column.

level: middleimportance: must knowfreq 50%
basics
~20 s

A hash function deliberately scatters keys: values that are adjacent in sort order land in unrelated buckets, and a prefix hashes to something unrelated to the whole value. The index stores no ordering, so ranges, prefixes, and sorted output are impossible without reading every bucket.

open as a page

In a hash index, what happens when two different key values map to the same bucket, and what does the index do when a bucket runs out of space?

level: middleimportance: should knowfreq 33%
basics
~20 s

Colliding keys simply share a bucket; the engine stores both and resolves it by comparing actual keys on read. When a bucket fills it chains an overflow page, or the index splits buckets to spread entries. Long chains turn constant-cost probes into linear scans.

open as a page

When would you deliberately create a hash index instead of a B+tree index on the same column, and why do most teams still default to the B+tree even for pure equality lookups?

level: seniorimportance: should knowfreq 38%
basics
~20 s

Choose hash only for high-cardinality columns queried exclusively by exact equality, especially with long keys where hash entries are much smaller. B+trees win by default because an equality probe is already near-free once upper levels are cached, and the same index also serves ranges, ordering, prefixes, and uniqueness.

open as a page

Queries on an orders table filter with status = 'ACTIVE' and created_at > (some timestamp). Should the multi-column index key be (status, created_at) or (created_at, status), and what general rule about equality and range predicates does your choice follow?

level: middleimportance: must knowfreq 70%
basics
~20 s

Use (status, created_at). Equality columns come first, the range column last. A range predicate on a leading column scatters the columns after it, so only one range column can be used for the seek and it must be the final key column used.

open as a page

A table has a single B+Tree index whose key is the column list (a, b, c). Which filter combinations can that index serve efficiently, and which cannot? Explain the leftmost-prefix rule behind your answer.

level: middleimportance: must knowfreq 78%
basics
~20 s

The index is sorted by a, then by b within equal a, then by c. It helps when the filter pins a leading prefix: a alone, a and b, or all three. Filters on only b, only c, or b and c cannot seek in it.

open as a page

A developer rewrites a query so the WHERE conditions appear in the same order as the columns of a multi-column index, expecting a faster plan. Does the textual order of predicates in the WHERE clause affect index usage? Explain.

level: juniorimportance: should knowfreq 55%
basics
~20 s

No. The optimizer normalizes the conjunction, so the order you type conditions in does not matter. What matters is which columns are constrained and how (equality vs range) versus the index key order, which is fixed at creation.

open as a page

A paginated query filters on one column and sorts by another, and the plan shows an expensive sort step. How can a multi-column index remove that sort entirely, and what must be true about the index column order and the ASC/DESC directions for that to work?

level: seniorimportance: should knowfreq 58%
basics
~20 s

Put the equality-filtered columns first and the sort columns immediately after, in the same order as the ORDER BY. Inside the pinned block the index is already in sort order, so the engine walks it and stops early. Mixed ASC/DESC only works if the index directions match or are exactly reversed.

open as a page

A busy table has accumulated a dozen single-column and multi-column indexes and writes have slowed. How would you decide which multi-column index keys the workload actually needs, and which existing indexes are safe to drop as redundant?

level: principalimportance: should knowfreq 45%
basics
~20 s

Collect the real query shapes, design one key per shape family with equality columns first and the range or sort column last, then drop any index whose key is a leftmost prefix of another. Verify with index usage counters and roll the drops out one at a time behind a quick rollback.

open as a page

What does it mean for an index to 'cover' a query, and what work does the database avoid when it can answer a query from the index alone?

level: juniorimportance: must knowfreq 66%
basics
~20 s

An index covers a query when every column the query needs — filters, joins, output, sorting — is present in the index. The engine then answers from the index alone and skips fetching the table rows, avoiding one random read per matching row.

open as a page

Several engines let you attach non-key payload columns to an index, spelled INCLUDE (...) in PostgreSQL and SQL Server. How do payload columns differ from putting those same columns at the end of the index key, and when would you choose each?

level: middleimportance: should knowfreq 42%
basics
~20 s

Payload columns are stored only in leaf entries: they can be returned but never used for seeking, ordering or uniqueness. Key columns are stored in internal nodes too and can do all three. Use payload for columns you only need to output, key columns when you must filter or sort by them.

open as a page

A plan reports an index-only scan, yet the query still performs a large number of table (heap) fetches. Why can an index entry alone be insufficient to return a row in an MVCC database, and what makes those extra fetches go away?

level: seniorimportance: should knowfreq 38%
basics
~20 s

Index entries carry no transaction visibility information, so the engine cannot always tell whether an entry points at a row version this transaction may see. When it cannot, it fetches the row to check. Keeping table maintenance current — so pages are marked all-visible — removes most of those fetches.

open as a page

You can make a hot query index-only by adding four more columns to an existing index on a write-heavy table. How would you decide whether that is worth doing, and what would you measure before and after?

level: principalimportance: should knowfreq 40%
basics
~20 s

Weigh the read saving — eliminated per-row table fetches times the query rate — against the write cost: every insert, delete and update of those columns now maintains a wider entry, plus extra space and cache pressure. Measure query latency, write latency, index size and cache hit rate before and after.

open as a page

What do the terms "cardinality" and "selectivity" mean when a database query optimizer reasons about a column or a filter predicate, and how do they decide whether using an index pays off?

level: juniorimportance: must knowfreq 68%
basics
~20 s

Cardinality is a row count: how many distinct values a column holds, or how many rows a plan step returns. Selectivity is the fraction of rows a predicate keeps. Indexes pay off when that fraction is tiny, because each match costs a separate row lookup.

open as a page

What statistics does a relational database keep about a table's columns for query planning, and what does a histogram give the optimizer that a plain distinct-value count cannot?

level: middleimportance: must knowfreq 52%
basics
~20 s

Typically: row count, page count, per-column distinct values, null fraction, min/max, most-common values with frequencies, and a histogram. A distinct-value count only supports an averaged estimate assuming even distribution; a histogram records the actual shape of the data, so skewed values and range predicates are estimated correctly.

open as a page

A report query that ran in under a second for months suddenly takes several minutes, with no change to the query text, the schema, or the indexes. How would you determine whether inaccurate optimizer statistics are the cause, and what would you do about it?

level: seniorimportance: must knowfreq 56%
basics
~20 s

Get an execution plan with both estimated and actual row counts, then walk it bottom-up looking for the first step where estimate and actual diverge by orders of magnitude. If the leaf estimates are wrong, refresh statistics on those tables, re-check the plan, and if it still misestimates, add deeper histograms or multi-column statistics.

open as a page

A 50-million-row orders table has a `status` column with three possible values, where roughly 99% of rows are 'COMPLETED', 1% are 'PENDING', and a handful are 'FAILED'. Is a plain B+Tree index on `status` worth creating? Explain your reasoning.

level: middleimportance: should knowfreq 55%
basics
~20 s

It depends on which value you query. Searching 'COMPLETED' matches ~49.5 million rows, so the optimizer will scan and ignore the index. Searching 'PENDING' or 'FAILED' is highly selective and the index helps a lot. Skewed low-cardinality columns are worth indexing only for their rare values.

open as a page

An optimizer estimates 3 rows for a filter on both a `city` column and a `postal_code` column, but the query actually returns about 40,000 rows, and the resulting plan is catastrophically slow. Why does this kind of underestimate happen, and what can be done about it?

level: seniorimportance: should knowfreq 42%
basics
~20 s

The optimizer assumes predicates on different columns are independent and multiplies their selectivities. City and postal code are almost perfectly correlated, so the true combined selectivity is roughly the postal code's alone, not the product. Fix it with multi-column/extended statistics, or by removing the redundant predicate.

open as a page

What is a partial index — an index created with a WHERE predicate so it contains only a subset of a table's rows — and what problem does it solve? Give a workload where it clearly pays off.

level: juniorimportance: must knowfreq 45%
basics
~20 s

A partial index indexes only rows matching a predicate, so it is smaller, cheaper to maintain, and better cached. It pays off when queries always target a small slice of the table — for example only unprocessed jobs in a queue table where most rows are already done.

open as a page

A query filters on a computed value — for example the lowercased form of an email column — and the plain index on that column is never used. Explain why, and how an expression (function-based) index fixes it.

level: middleimportance: must knowfreq 50%
basics
~20 s

An index stores the column's raw values, so it can only match predicates on those raw values. Wrapping the column in a function produces values the index never stored. An expression index stores the computed results instead, and the planner uses it when the query's expression matches the indexed one.

open as a page

A table stores several rows per user but at most one of them may be flagged active at a time. How would you enforce that with a unique index carrying a WHERE predicate, and what are the limits of that approach?

level: middleimportance: should knowfreq 34%
basics
~20 s

Create a unique index on user_id restricted by a predicate matching only active rows. Uniqueness is then enforced within that subset only, so many inactive rows per user are fine. Limits: it enforces one condition on one subset, and swapping the active row can conflict mid-transaction.

open as a page

You created an index restricted by a WHERE predicate, but the planner keeps ignoring it for the query you built it for. What must be true for the optimiser to use a predicate-restricted index, and why do parameterised queries often fail that test?

level: seniorimportance: should knowfreq 32%
basics
~20 s

The optimiser must prove the query's own conditions imply the index predicate, using only planning-time information. It reasons about constants and simple comparisons, not runtime parameter values, so a predicate compared against a bind parameter often cannot be proven and the index is skipped.

open as a page

Why must the expression behind a function-based index be deterministic, and what goes wrong if you index something whose result can change for the same stored row?

level: seniorimportance: nice to knowfreq 20%
basics
~20 s

The result is computed once at write time and stored as the index key. If the expression's output later changes for the same row, the key no longer matches the row, so lookups silently miss rows or return wrong ones. Only deterministic, row-only expressions are safe.

open as a page

Explain the difference between a table whose rows are stored in a clustered (index-organized) structure and a table stored as a heap, and how each locates a row.

level: juniorimportance: must knowfreq 62%
basics
~20 s

In clustered storage the table IS the index: rows live in the leaves of a B+tree ordered by the clustering key. In a heap, rows sit in unordered pages and are addressed by a physical row id, with every index a separate structure pointing at those ids.

open as a page

A table is stored clustered on its primary key. Compare what happens on insert when that key is a monotonically increasing value versus a random one such as a version-4 UUID.

level: middleimportance: must knowfreq 66%
basics
~20 s

An increasing key always inserts at the rightmost leaf: pages fill in order, few splits, compact table, small write set. A random key inserts everywhere, so many leaf pages are touched and split, pages end up half full, the table bloats and writes scatter across memory and storage.

open as a page

You are designing an OLTP table and can choose whether the rows are physically ordered by a clustering key or stored unordered in a heap. What workload characteristics push you toward each, and what do you give up?

level: seniorimportance: should knowfreq 42%
basics
~20 s

Cluster when one key dominates access, especially range or parent-child locality, and inserts arrive roughly in key order — you get no-row-fetch lookups and sequential ranges. Prefer a heap when access spreads across many columns, the key is wide, or inserts are random with respect to it.

open as a page

How does each storage model identify a row over time — a heap with physical row identifiers versus a clustered table keyed on the primary key — and what happens when a row moves or its primary key value is updated?

level: seniorimportance: should knowfreq 34%
basics
~20 s

A heap identifies rows by physical address, stable until the row is relocated, at which point engines leave a forwarding pointer or update index entries. A clustered table identifies rows logically by primary key, so rows may move between pages freely, but changing the key value physically relocates the row.

open as a page

A high-throughput ingest table is clustered on a monotonically increasing key, and insert throughput plateaus well below what the hardware should allow even though there is no I/O bottleneck. How would you reason about the cause and the options?

level: principalimportance: nice to knowfreq 28%
basics
~20 s

Every insert targets the same rightmost leaf page, so concurrent writers serialize on that one page's latch. Options: partition or shard the key space so there are several insert points, add a leading discriminator to spread writes, or accept it and scale out by table or node.

open as a page

Walk through exactly what a relational engine does when a query filters using a secondary (non-clustered) index but also selects columns that the index itself does not contain.

level: juniorimportance: must knowfreq 60%
basics
~20 s

It searches the secondary index to find the matching entries, and each entry carries a pointer to the row - either a physical row address or the primary key. For every match it then reads the actual row from the table to get the missing columns. Two lookups per row.

open as a page

Some engines store a physical row address (page and slot) in each secondary-index entry, while others store the table's primary-key value instead. Compare the two designs and their consequences for reads and for updates.

level: middleimportance: must knowfreq 48%
basics
~20 s

A physical address makes each lookup a single direct page read, but any row move must be repaired - either by updating every secondary index or by leaving a forwarding pointer. A primary-key locator survives row movement untouched, but every lookup costs a full B+Tree descent and entries are larger.

open as a page

Two queries each match about 50,000 rows through secondary indexes on the same table and each needs columns the index does not hold. One finishes in milliseconds, the other takes many seconds in row lookups. What property of the data explains the difference, and how would you confirm it?

level: seniorimportance: should knowfreq 36%
basics
~20 s

Correlation between the index key order and the physical order of rows. Well-correlated keys make consecutive lookups land on the same few pages, so they are cache hits or sequential reads; uncorrelated keys scatter 50,000 lookups across 50,000 different pages, each a random read. Confirm with the clustering statistic and buffer hit counts.

open as a page

A table stores its rows in primary-key order and someone proposes a 60-byte composite natural key as the primary key. The table already carries six secondary indexes. What are the consequences, and what would you propose instead?

level: seniorimportance: should knowfreq 42%
basics
~20 s

Every secondary-index entry embeds the primary key as its row locator, so a 60-byte key adds roughly 60 bytes per entry across all six indexes. Entries get wider, fanout drops, indexes grow, less of them fits in cache, and lookups and writes cost more. Prefer a narrow surrogate key plus a unique constraint on the natural key.

open as a page

Why does adding indexes to a table slow down INSERT, UPDATE and DELETE statements, and roughly how does that cost scale as more indexes are added?

level: juniorimportance: must knowfreq 66%
basics
~20 s

Indexes are separate ordered structures that must stay consistent with the table, so every write also writes to each affected index — in its own sorted position, on a different page, inside the same transaction and its log. One insert into a table with five indexes is six structures updated, not one.

open as a page

On a busy production database, how would you identify indexes that are unused or redundant, and what would you verify before dropping one?

level: seniorimportance: must knowfreq 50%
basics
~20 s

Read the engine's per-index usage counters over a full business cycle to find never-scanned indexes, and compare definitions to find ones whose columns are a leading prefix of another index. Before dropping: confirm the counters cover all replicas and periodic jobs, check the index does not back a constraint, and make it reversible.

open as a page

When loading tens of millions of rows into an existing table, teams often drop the table's indexes first and rebuild them after the load. Why is that faster, and when would it be the wrong choice?

level: middleimportance: should knowfreq 38%
basics
~20 s

Maintaining indexes row by row means a random page touch per index per row, with splits and logging. Building an index once afterwards reads the data in bulk, sorts it, and writes packed leaf pages sequentially. It is wrong when the table stays online, when constraints must hold during the load, or when the existing data dwarfs the new rows.

open as a page

Does an UPDATE statement that changes only columns with no index on them still incur index maintenance cost? Explain what determines the answer.

level: middleimportance: should knowfreq 42%
basics
~20 s

Logically, only indexes containing a changed column need their keys updated. Physically, it depends on whether the row stays in place: if the storage engine writes a new row version elsewhere, every index pointing at that row must be updated too, even for untouched columns.

open as a page

A write-heavy OLTP table has accumulated fourteen indexes, one added for each new report over three years, and insert latency has been climbing. How would you decide what the right number of indexes for that table is?

level: principalimportance: should knowfreq 32%
basics
~20 s

There is no magic number. Derive it from the table's write service level: measure the per-index write cost, attribute each index to a named query with a business owner, drop what nothing claims, consolidate overlapping ones, and move report-only access to a replica or a separate model.

open as a page

A table's live row count has been flat for months, yet queries served by one of its B+tree indexes keep getting slower. What can happen inside an index over time that explains this, even with no growth in live data?

level: juniorimportance: must knowfreq 55%
basics
~20 s

Deletes and updates leave dead entries and half-empty pages behind. The same live keys end up spread over more pages, so scans read more pages and each cached page carries less useful data. That is index bloat.

open as a page

How would you determine whether a specific index in a production database is genuinely bloated, rather than simply large or slow for some other reason?

level: seniorimportance: must knowfreq 45%
basics
~20 s

Compare the index's physical size against an estimate of what its live entries need: live row count times average entry width, divided by page capacity. A large ratio, plus falling page density and rising scan I/O over time, indicates bloat.

open as a page

An index in a busy production database has been measured as heavily bloated. What are the ways to reclaim that space, and what does each cost in terms of locking, extra disk, duration and risk while the system keeps serving traffic?

level: seniorimportance: must knowfreq 50%
basics
~20 s

Options: an offline rebuild (fast, needs an exclusive lock), an online/concurrent rebuild (builds a shadow copy while writes continue — slower, needs space for both copies, can fail and leave an unusable index), an in-place reorganize (no full copy, less thorough), or dropping the index if unused.

open as a page

What does the fill factor (page fill percentage) of a B+tree index mean, why do engines let you leave free space in a leaf page on purpose, and how does a page's fill decay under different insert and update patterns?

level: middleimportance: should knowfreq 40%
basics
~20 s

Fill factor is how full a leaf page is packed when the index is built. Leaving free space lets later entries land in place instead of forcing new pages. Random churn drives density down; append-only keys keep it high.

open as a page

A team proposes a nightly job that rebuilds every index in the database to "keep things fast." How would you evaluate that policy, and what would you propose instead?

level: principalimportance: should knowfreq 28%
basics
~20 s

Rebuilding indiscriminately burns I/O, log and replication bandwidth on indexes that are healthy, and it treats a symptom. Replace it with measurement-driven, targeted maintenance plus structural fixes: drop unused indexes, unblock reclamation, partition delete-heavy tables.

open as a page

Bitmap indexes are conventionally recommended for low-cardinality columns. Explain why that guidance exists, and what actually happens to the index as the number of distinct values grows into the millions.

level: middleimportance: must knowfreq 34%
basics
~20 s

Storage grows with distinct values because each value needs its own bit vector, and the benefit comes from each value matching many rows. At very high cardinality the bitmaps become extremely sparse, per-value overhead dominates, and each value matches so few rows that a B+Tree does the job better.

open as a page

Why are bitmap indexes considered unsuitable for tables receiving concurrent row-level updates from many transactions, yet perfectly acceptable on a table that is rebuilt or loaded in a nightly batch?

level: seniorimportance: must knowfreq 33%
basics
~20 s

Bitmaps are stored as compressed chunks covering ranges of rows, so changing one row means decompressing, editing and rewriting a whole chunk, and the lock covers every row in it. Two transactions updating unrelated rows in the same chunk block each other, producing serialization and deadlocks.

open as a page

What is a bitmap index, and how does it physically represent the set of rows that match a particular column value?

level: juniorimportance: should knowfreq 35%
basics
~20 s

A bitmap index stores one bit vector per distinct column value, with one bit per row: bit set means that row has that value. Answering a predicate means fetching one bitmap and turning its set bits back into row locations, and combining predicates is bitwise AND or OR.

open as a page

A query filters a very large table on five different columns at once, none of which is selective on its own. Explain how a bitmap-based access path combines those predicates, and why that can beat choosing the single best index and filtering the rest.

level: middleimportance: should knowfreq 28%
basics
~20 s

The engine gets a bitmap of matching rows for each predicate, then bitwise ANDs them — 64 rows per machine word — producing the final row set before touching the table. Only surviving rows are fetched, and in physical order, instead of fetching everything one predicate matched.

open as a page

A query plan on a table that has only ordinary B+Tree indexes shows a bitmap being built from an index scan and then used to read the table. What is the engine doing there, why does it bother building a bitmap, and what does it cost?

level: seniorimportance: should knowfreq 32%
basics
~20 s

It scans the index and collects matching row locations into an in-memory bitmap instead of fetching each row immediately. That lets it visit table pages once each in physical order instead of randomly, and lets it combine several indexes with bitwise AND or OR before touching data.

open as a page

A relational database can read the rows a query needs either by reading the whole table or by going through an index. Describe what physical work each of those two access paths performs, step by step.

level: juniorimportance: must knowfreq 78%
basics
~20 s

A full scan reads every page of the table in order and discards rows that do not match. An index scan descends the index to the matching keys, then follows each key's pointer to fetch that row's table page. Fewer rows touched, but scattered reads.

open as a page

For a query that matches a large fraction of a table's rows, reading the entire table can be cheaper than using a perfectly valid index on the filtered column. Explain why that happens, and roughly what fraction of rows is the tipping point.

level: middleimportance: must knowfreq 72%
basics
~20 s

The index path pays roughly one random page read per matching row, on top of index reads. A full scan pays one cheap sequential read per table page, no matter how many rows match. Past a few percent of rows matched, the scan wins.

open as a page

Some queries can be answered by reading only index pages, never touching the table's own data pages. What must be true for that access path to be possible, and how much I/O does it actually save compared with an index lookup that then fetches rows?

level: middleimportance: should knowfreq 55%
basics
~20 s

Every column the query touches, in the output and in the predicates, must exist in the index. Then the engine skips the per-row table fetch entirely, eliminating the random I/O and reading only narrow index leaf pages in order.

open as a page

You need to estimate, in pages of I/O, the cost of retrieving 200,000 matching rows from a 10-million-row table through an index versus reading the whole table sequentially. Walk through the arithmetic and state the assumptions you would make.

level: seniorimportance: should knowfreq 45%
basics
~20 s

Convert rows to pages. With 50 rows per page the table is 200,000 pages, read sequentially. The index path costs a few index pages plus up to 200,000 random row fetches, each several times more expensive. The scan wins by roughly an order of magnitude.

open as a page

Explain why applying a function to an indexed column, or comparing an indexed text column to a numeric value, stops a B+Tree index from being used - and what you can do about each case without changing what the query returns.

level: middleimportance: must knowfreq 66%
basics
~20 s

A B+Tree is sorted by the raw column value, so it can only seek ranges of that value. A function or an implicit cast on the column produces a different value per row, forcing the engine to compute it for every row. Move the transformation to the constant, or index the expression.

open as a page

You create an index on a column, but the database still reads the whole table for a query that filters on that column. What are the reasons an optimizer skips a usable index, and how would you narrow down which one applies?

level: middleimportance: must knowfreq 72%
basics
~20 s

Either the index cannot be used (a function or cast wraps the column, a leading wildcard, wrong leading column) or the optimizer decided it is not worth it (the filter matches a large fraction of rows, the table is tiny, or row estimates are wrong from stale statistics). Read the actual plan.

open as a page

After a large bulk load, a query that had reliably used an index started doing full table scans. How do optimizer statistics drive that decision, and how would you diagnose and correct a cardinality misestimate in production?

level: seniorimportance: must knowfreq 55%
basics
~20 s

The optimizer estimates matching rows from sampled statistics - histograms, distinct-value counts, common values - then costs plans. Stale or out-of-range statistics make the estimate wrong, so it prices the index badly. Diagnose by comparing estimated with actual rows in the plan, then refresh statistics and add multi-column ones for correlated predicates.

open as a page

A B+Tree index can accelerate a pattern match anchored at the start of a string, such as matching 'abc' followed by anything, but not one that searches for 'abc' anywhere inside the value. Why, and what are the options when you genuinely need infix search?

level: juniorimportance: should knowfreq 52%
basics
~20 s

B+Tree entries are sorted by the whole string from the first character, so a known prefix defines one contiguous key range. A pattern that can start anywhere has matches scattered across the whole index, so the engine must read every value. Infix search needs a different index type.

open as a page

A predicate matches about 30 percent of a ten-million-row table and a perfectly suitable index exists on that column, yet the optimizer chooses a full table scan. Explain why that can genuinely be the cheaper plan.

level: middleimportance: should knowfreq 50%
basics
~20 s

Index access costs one random page fetch per matched row on top of walking the index; a scan reads pages sequentially with read-ahead and touches each page once. Past a few percent of the table the index path reads more pages than the table has, so scanning wins.

open as a page