Skip to content

CONCEPT Cited by 1 source

Per-query used-index set

The per-query used-index set is the collection of indexes the storage engine touched during a single query execution. For a query hitting a composite index once it's a singleton set; for a query with OR-predicates or UNION branches it's multiple indexes; for a full-table scan it's empty.

Canonicalised by PlanetScale's Insights as the unit of index-usage telemetry. (Source: sources/2026-04-21-planetscale-tracking-index-usage-with-insights.)

Granularity comparison

Primitive Scope Source
Plan-time index hint static, a-priori EXPLAIN
Global per-(table,index) counter server lifetime, since restart performance_schema.table_io_waits_summary_by_index_usage
Per-query used-index set single execution InnoDB handler hook (this concept)
Per-pattern time-series of index counts per-pattern per-interval Insights Indexes tab (see concepts/index-usage-time-series)

The per-query set is the finest-grained primitive — one per execution — and is the substrate from which per-pattern aggregates are derived.

Capture mechanism

Via the InnoDB index_init() hook: InnoDB's storage handler calls this function once per (query, index) pair prior to use; the patched fork appends the index name to a per-query data structure; at query completion the set is returned to the client in the final MySQL wire-protocol packet. See patterns/handler-hook-sidecar-telemetry for the general shape.

Why the set is interesting

A query pattern's used-index set varies across executions with the same fingerprint:

  • Queries with different literals may hit different indexes via optimiser choice (e.g. WHERE region = 'us-east-1' vs WHERE region = 'eu-west-1' with differently-distributed index cardinality).
  • Planner flips between two candidate indexes as table statistics shift.
  • EXPLAIN-time plan differs from actual run-time plan when adaptive hash index or materialisation-hinting kicks in.

Time-series aggregation of per-query sets across a fingerprint therefore surfaces optimiser flapping as a shifting mix of index-hit percentages, not a static plan.

Empty-set semantics

The empty set means "no index was used" — i.e. a full-table scan. In Insights this is productised as the indexed:false search predicate. The empty-set case is the load-bearing signal for missing-index diagnosis.

Caveats

  • SELECT-only in Insights — see concepts/select-only-index-telemetry-caveat. The per-query set is only captured for SELECT queries; UPDATE / DELETE index usage is not reported.
  • Storage-engine-specific — the InnoDB index_init hook capture only covers InnoDB. MyISAM / MEMORY / ARCHIVE / CSV / federated tables would need separate handler patches.
  • No per-row attribution — the set records which indexes were opened for the query, not which indexes contributed how many row reads. For per-(table,index) row-count detail, the table_io_waits_summary_by_index_usage pattern still provides finer detail at the cost of per-query attribution.
  • Primary key vs secondary index — a query that walks only the clustered index (primary key) appears with PRIMARY in its set in MySQL's performance_schema convention. Insights presumably follows the same convention, but the post doesn't canonicalise the naming.

Seen in

  • sources/2026-04-21-planetscale-tracking-index-usage-with-insights — Rafer Hazen (2024-08-14) canonicalises the per-query used-index set as the granularity primitive beneath Insights' new Indexes tab. Captured via a patched InnoDB index_init hook into a per-query data structure; returned in the final MySQL wire-protocol packet; aggregated by VTGate per fingerprint; flushed into the Insights pipeline every 15 seconds. 100% coverage at "negligible overhead". SELECT queries only — UPDATE / DELETE do not contribute.
Last updated · 470 distilled / 1,213 read