Home/Learn/MySQL/Index Selectivity & Cardinality

Index Selectivity & Cardinality

Intermediate
Indexes

High 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.

SQL — selectivity calculation and cardinality inspection
-- 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.

SQL — EXPLAIN-based optimiser analysis and selective composite index
-- 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 order

Composite 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.

SQL — prefix length analysis and composite index for mixed-selectivity queries
-- 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 set

Key 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 Aria
1

What is index selectivity and why does a low-selectivity index sometimes cause the optimiser to do a full scan?

MediumOracle
2

You added an index on the "status" column but EXPLAIN shows key=NULL. What is the likely cause?

MediumAmazon
3

How would you design a composite index for WHERE status = X ORDER BY created_at DESC?

HardFlipkart
4

What is a prefix index and when is it useful? What are its limitations?

MediumThoughtworks
5

How do you refresh cardinality statistics in MySQL and when is this necessary?

EasyBooking.com

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.

Loading discussion…