Optimizer statistics, histograms and hints

MySQL · Expert · 15 min

When the optimizer picks the wrong index, these are the tools that fix it without rewriting the application.

MySQL's cost-based optimizer estimates rows from statistics:

  • Index statistics (cardinality) are sampled from a number of random pages, persisted (innodb_stats_persistent = ON) and recalculated automatically after about 10% of rows change, or on ANALYZE TABLE. Raise STATS_SAMPLE_PAGES per table when estimates for skewed data are poor.
  • Histograms (8.0) describe value distributions of non-indexed columns, so the optimizer can estimate selectivity of WHERE status = 'refunded' and pick a better join order: ANALYZE TABLE orders UPDATE HISTOGRAM ON status WITH 32 BUCKETS. They aren't refreshed automatically by ANALYZE TABLE alone; rebuild them as data changes.
  • For equality ranges, the optimizer does index dives (actually probing the index) up to eq_range_index_dive_limit values in an IN list, then falls back to statistics.

When it still chooses badly:

  • Optimizer hints in a comment after SELECT: /*+ INDEX(o idx_customer) */, NO_INDEX, JOIN_ORDER(a, b), HASH_JOIN/NO_HASH_JOIN, MAX_EXECUTION_TIME(1000), SET_VAR(optimizer_switch = '...'). They apply to one statement and are ignored if invalid.
  • Older index hints: FORCE INDEX (idx), USE INDEX, IGNORE INDEX.
  • The optimizer trace (SET optimizer_trace = 'enabled=on', then read information_schema.optimizer_trace) shows every alternative considered and its cost.

Prefer fixing statistics over hints: hints freeze a plan that may become wrong as data changes.

Example

ANALYZE TABLE orders UPDATE HISTOGRAM ON status WITH 32 BUCKETS;
SELECT column_name, JSON_EXTRACT(histogram, '$."histogram-type"') AS type
FROM information_schema.column_statistics WHERE table_name = 'orders';
-- status | "singleton"

ALTER TABLE orders STATS_SAMPLE_PAGES = 200;
ANALYZE TABLE orders;                          -- refresh index statistics

SELECT /*+ INDEX(o idx_customer) MAX_EXECUTION_TIME(2000) */ o.id, o.total
FROM orders o
WHERE o.customer_id = 42 AND o.status = 'refunded';

SET optimizer_trace = 'enabled=on';
SELECT id FROM orders WHERE customer_id = 42 AND status = 'refunded';
SELECT JSON_EXTRACT(trace, '$**.considered_access_paths')
FROM information_schema.optimizer_trace;
SET optimizer_trace = 'enabled=off';

Remember

  • Index statistics are sampled; ANALYZE TABLE refreshes them
  • Histograms give the optimizer distributions for non-indexed columns
  • Use /+ hints / sparingly; the optimizer trace explains its choices

Watch out for

  • Adding FORCE INDEX everywhere instead of fixing statistics, then suffering when data distribution changes.

Check yourself

  1. What do MySQL histograms help the optimizer estimate?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: dev.mysql.com/doc/refman/8.4/en/optimizer-statistics.html

Builds on

Unlocks

Nothing builds on this yet.