Index Selectivity & Cardinality
IntermediateHigh selectivity (many distinct values) makes an index effective; cardinality statistics in information_schema.statistics guide the optimiser's index selection decisions.
Overview
Index selectivity is the ratio of distinct values to total rows. High selectivity (close to 1.0) means the index is very discriminating — each lookup returns few rows. Low selectivity (close to 0) means the index is broad — each value matches many rows. MySQL's query optimiser uses cardinality statistics (stored per index in information_schema.statistics) to decide whether to use an index. If selectivity is too low — typically < 5% of rows filtered — the optimiser may prefer a full table scan using the buffer pool over random index lookups. Understanding selectivity helps explain why EXPLAIN shows "possible_keys" contains your index but "key" is NULL.
Measuring selectivity and cardinality
Cardinality = number of distinct values the optimiser estimates for an index column. Higher cardinality = higher selectivity = index is more useful. Check with SHOW INDEX or information_schema. Run ANALYZE TABLE to refresh statistics after large data changes.
-- Check index cardinality (higher = more selective)
SHOW INDEX FROM orders;
-- Columns: Key_name, Column_name, Cardinality
-- Cardinality is an estimate — refreshed by ANALYZE TABLE
-- Manually calculate selectivity for a column
SELECT
COUNT(DISTINCT status) / COUNT(*) AS status_selectivity,
COUNT(DISTINCT customer_id) / COUNT(*) AS customer_selectivity,
COUNT(DISTINCT id) / COUNT(*) AS id_selectivity
FROM orders;
-- status: 0.00001 (5 distinct values / 500k rows) → LOW selectivity
-- customer_id: 0.05 (25k customers / 500k rows) → MEDIUM selectivity
-- id: 1.0 (500k / 500k) → HIGH selectivity (primary key)
-- Low-selectivity index example: status has 5 values
-- Querying WHERE status = 'PLACED' returns 20% of rows
-- Optimiser may prefer full scan over index lookup
-- Refresh statistics after bulk load
ANALYZE TABLE orders;
-- information_schema cardinality
SELECT INDEX_NAME, COLUMN_NAME, CARDINALITY
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = 'shop' AND TABLE_NAME = 'orders'
ORDER BY SEQ_IN_INDEX;Selectivity threshold and optimiser decisions
The optimiser compares the cost of an index lookup (random I/O for each matching row) against a full table scan (sequential I/O of the whole table buffered in memory). The crossover point is typically when an index would read > 20–30% of rows. EXPLAIN shows whether the optimiser used the index. Use FORCE INDEX to override the decision and compare query cost.
-- EXPLAIN shows optimiser decision
EXPLAIN SELECT * FROM orders WHERE status = 'PLACED';
-- If 'status' index has low selectivity:
-- type: ALL (full scan preferred over index)
-- key: NULL
-- rows: 500000
-- Compare with high-selectivity column
EXPLAIN SELECT * FROM orders WHERE customer_id = 12345;
-- type: ref (index lookup)
-- key: idx_customer_id
-- rows: 20
-- Force index to compare execution plans
EXPLAIN SELECT * FROM orders FORCE INDEX (idx_status)
WHERE status = 'PLACED';
-- rows: 100000 (higher than full scan estimate — confirms optimiser was right)
-- When to index low-selectivity columns:
-- 1. Combined with high-selectivity columns (composite index)
-- 2. The WHERE + ORDER BY pattern aligns with composite index
CREATE INDEX idx_status_created ON orders (status, created_at DESC);
-- Now: WHERE status = 'SHIPPED' ORDER BY created_at DESC
-- is efficient — status filters, created_at provides sort orderComposite index selectivity and prefix indexes
A composite index's selectivity is determined by the leading columns. Adding a high-selectivity column as the second part dramatically improves a low-selectivity leading column. Prefix indexes on long VARCHAR/TEXT columns index only the first N characters — trading selectivity for storage. Analyse the optimal prefix length before creating.
-- Optimal prefix length analysis for VARCHAR(500) column
SELECT
COUNT(DISTINCT LEFT(description, 5)) / COUNT(*) AS sel_5,
COUNT(DISTINCT LEFT(description, 10)) / COUNT(*) AS sel_10,
COUNT(DISTINCT LEFT(description, 20)) / COUNT(*) AS sel_20,
COUNT(DISTINCT description) / COUNT(*) AS sel_full
FROM products;
-- sel_5: 0.32, sel_10: 0.68, sel_20: 0.91, sel_full: 0.98
-- → prefix of 20 chars gives 93% of full column selectivity at much lower storage
-- Create prefix index
CREATE INDEX idx_description_prefix ON products (description(20));
-- Prefix indexes CANNOT be used for ORDER BY or covering index lookups
-- Only useful for = and LIKE 'prefix%' — NOT LIKE '%suffix%'
-- Composite index: status (low) + created_at (high cardinality date)
-- Leading column filters; second column provides ordering
EXPLAIN SELECT * FROM orders
WHERE status = 'SHIPPED' ORDER BY created_at DESC LIMIT 50;
-- With idx_status_created: type=ref, Extra="Using index condition"
-- Without: filesort on large result setKey Points to Remember
- 1Selectivity = distinct values / total rows; high selectivity (→ 1.0) makes an index highly effective
- 2Cardinality is the optimiser's estimate of distinct values; run ANALYZE TABLE to refresh after bulk changes
- 3If an index would return > 20–30% of rows, the optimiser often prefers a full table scan (cheaper sequential I/O)
- 4EXPLAIN shows "key: NULL" and "type: ALL" when the optimiser rejects an available index due to low selectivity
- 5Low-selectivity leading columns (status, boolean flags) benefit from high-selectivity second columns in composite indexes
- 6Prefix indexes (description(20)) reduce storage on long text columns but cannot serve ORDER BY or covering index lookups
Interview Questions
Sign in to ask AriaWhat is index selectivity and why does a low-selectivity index sometimes cause the optimiser to do a full scan?
You added an index on the "status" column but EXPLAIN shows key=NULL. What is the likely cause?
How would you design a composite index for WHERE status = X ORDER BY created_at DESC?
What is a prefix index and when is it useful? What are its limitations?
How do you refresh cardinality statistics in MySQL and when is this necessary?
Ask Aria about Index Selectivity & Cardinality
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.