Index advisor: heuristic and limits
Inputs
- The slowest example statement of each fingerprint, parsed by
querylens.sqlshape(tables and aliases, WHERE / JOIN ... ON predicates, ORDER BY, GROUP BY, LIMIT, OFFSET, subqueries and CTEs as separate scopes, UNION branches as separate scopes). - The schema (
CREATE TABLE,CREATE [UNIQUE] INDEX,ALTER TABLE ... ADD CONSTRAINT PRIMARY KEY/UNIQUE, sopg_dump -soutput works) giving columns, types and existing indexes.
Candidate construction (per table in each scope)
- Equality columns:
=,IN,= ANY,IS NULL, plus the join key of the table that is probed (the one that appears later in the FROM list). - One range column:
<,>,<=,>=,BETWEEN,LIKE 'prefix%'. Only the first is used, because columns after a range column cannot narrow the index scan. - Trailing columns: the ORDER BY columns (when all belong to the table, none is an expression); otherwise GROUP BY columns.
- Predicates wrapped in functions (
lower(email) = ?),<>,NOT IN, leading-wildcard LIKE and OR groups are not indexable by a plain btree and contribute nothing. - Cap at
--max-columns(default 4).
Worked example: WHERE customer_id = ? AND created_at >= ? ORDER BY created_at DESC LIMIT 20 gives equality [customer_id], range [created_at], trailing [created_at] (already present) and the candidate shop_order(customer_id, created_at).
An ORDER BY with LIMIT and no predicates on the table yields an order-only candidate; with unusable predicates present it does not, since scanning an index in order and filtering is rarely a win.
Dedupe against the schema
A candidate is dropped when an existing plain btree index already serves it: the first k index columns equal the candidate's k equality columns as a set (equality order is irrelevant), and the following columns match exactly. Partial and non-btree indexes never count. Candidates whose equality columns contain a whole unique index are skipped (the lookup returns at most one row). If the new index extends an existing shorter non-unique one, the existing one is listed under supersedes.
Candidates that are prefixes of another candidate on the same table are merged into the longer one, and their benefit is added.
Benefit and redundancy
Benefit is the sum of total time of the fingerprints a recommendation helps. It is an upper bound on time saved, not a prediction: it is the whole statement time, including parts an index cannot speed up.
Redundant existing indexes are btree, non-partial indexes whose columns are a prefix of another index. Identical duplicates keep the PK, then a unique index, then the earlier one. Unique and primary-key indexes are never suggested for dropping, since they enforce a constraint.
Limits
- No statistics: selectivity, table size and cardinality are unknown, so low-selectivity leading columns (boolean flags) are not reordered.
- The advisor follows "equality, then range, then ORDER BY". When a range column precedes ORDER BY columns the index can filter but not return rows in order; the trailing columns are still emitted, as a cheap partial help.
- Unqualified columns in multi-table queries resolve only through the schema; if ambiguous they are skipped.
- Expressions, casts on columns, JSON paths, covering (
INCLUDE) indexes, and MySQL index hints are not modelled. Identifiers are compared case-insensitively. - Index write cost is not weighed; review each recommendation against write-heavy tables.