skip to content

Indexing and Query Performance

The performance habits a query author controls: sargable predicates, avoiding implicit casts, pruning columns instead of SELECT *, choosing between EXISTS, IN and JOIN, keyset pagination, batched DML, and reading EXPLAIN on your own query. Interviewers ask because a slow query is usually fixed by rewriting it, not by adding one more index.

part ofSQLoverview, primer and where to startread it →
on this pageshow

questions

page 2 of 2

A report uses INNER JOIN plus SELECT DISTINCT only to filter — what does that cost at scale?

level: seniorimportance: should knowfreq 52%

basics

~20 s

The join multiplies each parent row by its number of matching children, and DISTINCT then sorts or hashes that whole intermediate at full row width to undo it. You pay to build rows and pay again to discard them; a semi-join never builds them.

open as a page

Why can a bind parameter's declared type make a query slower than the same literal?

level: seniorimportance: should knowfreq 40%

basics

~20 s

The client driver, not the server, decides a bind parameter's SQL type. If that type does not match the column, the server sees a cross-type comparison and converts the column per row, losing the index — even though the hand-typed literal version was fine.

open as a page

How does a collation or character-set mismatch between two joined VARCHAR keys hurt the join?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Comparing two character values requires one agreed collation. When the joined columns declare different collations or character sets, the engine either refuses the comparison or converts one side for every row, which removes that column's index as a way to drive the join.

open as a page

In keyset pagination, how do you query the previous page?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Anchor on the first row of the current page, flip both the comparison and the ORDER BY, and take the same LIMIT, then reverse those rows in an outer query to restore display order. Flipping only one of the two returns the wrong rows.

open as a page

With OFFSET paging over a live feed, why do users see duplicated rows?

level: seniorimportance: should knowfreq 46%

basics

~20 s

OFFSET counts positions in a result recomputed on every request. Rows inserted ahead of the current position push everything down, so the next page repeats rows already shown; deletions pull rows up and skip them. Keyset anchors on a value, so the boundary stays fixed.

open as a page

The ORDER BY column is indexed and the query ends in FETCH FIRST 20 ROWS ONLY, yet it reads millions of rows. Why?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Index order only survives if nothing between the scan and the row limit discards it — grouping, DISTINCT, hashing or an ordering key computed later all reintroduce a sort. And even a preserved order reads far past 20 rows when a selective filter rejects most entries.

open as a page

A search endpoint uses WHERE (:city IS NULL OR city = :city) for each optional filter — what does that cost, and how do you fix it?

level: seniorimportance: should knowfreq 38%

basics

~20 s

One statement covering every combination of supplied and omitted filters forces a single plan that must be valid when any parameter is NULL, so no index can be committed to and the engine typically scans. Build the statement from the filters actually supplied, binding values as parameters.

open as a page

After rewriting a slow query, how do you use plans to prove the rewrite actually helped?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Compare measured plans, not estimates: run both versions with EXPLAIN ANALYZE on the same data and parameters, discard the first cold run, and confirm both queries return identical result sets before trusting any timing difference.

open as a page

When a predicate like UPPER(email) = ? cannot be range-rewritten, how do you keep it index-friendly?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Stop transforming the column: normalize the values on write so the stored form is already canonical, then compare the bare column to a normalized parameter. If the stored data cannot be normalized, the fix moves into the schema — an indexed expression or a persisted generated column — not the WHERE clause.

open as a page

A list endpoint using SELECT * slowed sharply after a large TEXT column was added — why?

level: seniorimportance: should knowfreq 45%

basics

~20 s

The star silently grew: the same query now projects kilobytes per row that nobody renders. Payload, server buffers and any buffered sort all widen at once, and engines that store oversized values apart from the row must now fetch them.

open as a page

Does writing SELECT 1 instead of SELECT * inside an EXISTS subquery make it faster?

level: middleimportance: nice to knowfreq 32%

basics

~10 s

No. EXISTS tests only whether the subquery produces a row, so its select list is not evaluated for values. SELECT 1, SELECT * and SELECT NULL all behave identically; the choice is purely stylistic.

open as a page

showing 31–41 of 41