← Home ← Codex ← DEBT ← Engine
Browse by Category
+ added · updated 7d
← Back to glossary

Index Selectivity & Cardinality

Performance PHP 5.0+ Intermediate
debt(d5/e3/b5/t7)
d5 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'specialist tool catches it' (d5), because the detection_hints.tools list includes mysql-explain, pt-query-digest, and mysql-workbench — all specialist database tools. The code_pattern confirms the signal: EXPLAIN output showing many rows examined despite an index existing is only visible when you run EXPLAIN explicitly, not through a default linter or compiler. The problem is silent in normal development and requires deliberate profiling or query analysis.

e3 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3), grounded in the quick_fix: reordering composite index column order or replacing a low-cardinality leading column with a high-cardinality one (e.g. swapping boolean first-column for user_id). This is typically a targeted schema change — DROP INDEX then CREATE INDEX with corrected column order — affecting one table/component. It's more than a one-line patch because it may require a migration and validation, but it doesn't span the codebase.

b5 Burden Structural debt — long-term weight of choosing wrong

Closest to 'persistent productivity tax' (b5), because index decisions apply to both web and cli contexts per applies_to, and a poorly selective index silently degrades query performance across any code path that touches the indexed table. Every future query added against that table is shaped by the existing index strategy, and the tags (performance, database, sql, indexes) confirm this is a cross-cutting database-layer concern. It doesn't define the entire system's shape, but it persistently taxes many work streams that hit the affected table.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap — contradicts how a similar concept works elsewhere' (t7), grounded in the misconception: developers familiar with indexing in general believe 'any column in a WHERE clause should have an index,' which is a reasonable and widely-taught rule of thumb. The contradiction is that adding an index on a low-cardinality column (the natural thing to do) can actively harm performance because the optimizer skips it or incurs double I/O. The common_mistakes reinforce this: assuming an index exists means it will be used directly contradicts the intuitive mental model, and composite index column ordering being counterintuitive adds another layer of surprise.

About DEBT scoring →

Also Known As

cardinality index index cardinality high selectivity index

TL;DR

High-cardinality columns (user IDs, emails) make effective indexes; low-cardinality columns (boolean, status with 3 values) rarely benefit from single-column indexes.

Explanation

Index selectivity is the ratio of distinct values to total rows. A unique column (email) has selectivity 1.0 — every lookup eliminates all but one row, maximising index efficiency. A boolean column (is_active) has selectivity ~0.5 — the index often leads to a full scan anyway because the planner estimates retrieving 50% of rows is cheaper than using the index. Cardinality is the count of distinct values — CHECK cardinality with SELECT COUNT(DISTINCT col) / COUNT(*). Composite index column order matters: put the highest-cardinality, most-frequently-filtered column first. For low-cardinality columns like status, use partial indexes (WHERE status = 'pending') in PostgreSQL to index only the relevant subset, dramatically reducing index size and improving selectivity.

Common Misconception

Any column in a WHERE clause should have an index. Low-selectivity columns (e.g. a boolean status with 95% true) make indexes counterproductive — the database reads the index then fetches most of the table anyway. High-selectivity columns (unique IDs, emails) benefit most from indexes.

Why It Matters

Index selectivity measures how many unique values an index contains relative to total rows — high-selectivity indexes (unique IDs) are used by the optimizer; low-selectivity ones (boolean, status) are often skipped.

Common Mistakes

  • Indexing a boolean column expecting it to speed up queries — the optimizer often ignores it.
  • Not checking cardinality with SHOW INDEX or pg_stats before adding an index.
  • Composite index column order not matching query selectivity — put the most selective column first.
  • Assuming an index exists means it will be used — the query planner makes that decision based on selectivity and row counts.

Code Examples

✗ Vulnerable
-- Low selectivity index — optimizer likely ignores it:
CREATE INDEX idx_users_active ON users (is_active); -- Only 2 values: 0 or 1
-- For 1M users, 900K are active — index not selective enough to be useful

-- High selectivity — optimizer uses it:
CREATE INDEX idx_users_email ON users (email); -- Near-unique per row
✓ Fixed
-- Check cardinality before adding an index
SELECT
    COUNT(DISTINCT status) AS unique_values,
    COUNT(*) AS total_rows,
    ROUND(COUNT(DISTINCT status) / COUNT(*) * 100, 2) AS selectivity_pct
FROM orders;
-- status has 4 values / 1M rows = 0.0004% selectivity → poor index candidate alone
-- user_id has 100k values / 1M rows = 10% → decent; combine with status for composite

-- Partial index for low-cardinality column (PostgreSQL)
-- Only index the minority case that benefits from fast lookup
CREATE INDEX idx_orders_pending ON orders(created_at)
    WHERE status = 'pending'; -- 2% of rows — now highly selective

Added 15 Mar 2026
Edited 13 Jun 2026
Views 72
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 1 ping T 1 ping W 1 ping T 0 pings F 0 pings S 1 ping S 1 ping M 0 pings T 1 ping W 0 pings T 0 pings F 0 pings S 1 ping S 1 ping M 0 pings T 0 pings W 1 ping T 2 pings F 0 pings S 0 pings S 0 pings M 1 ping T 0 pings W
No pings yet today
Brave Search 1
Amazonbot 10 Perplexity 8 Scrapy 7 Ahrefs 5 Google 4 SEMrush 3 PetalBot 3 Unknown AI 2 ChatGPT 2 Brave Search 2 Majestic 1 Bing 1 Meta AI 1 Sogou 1 Twitter/X 1 Applebot 1
crawler 49 crawler_json 3
DEV INTEL Tools & Severity
🟠 High ⚙ Fix effort: Medium
⚡ Quick Fix
Index columns with high cardinality first in composite indexes — a boolean column (2 values) makes a poor leading index key; user_id or email (millions of values) makes an excellent one
📦 Applies To
PHP 5.0+ web cli
🔗 Prerequisites
🔍 Detection Hints
Index on boolean or low-cardinality status column as first column; EXPLAIN showing many rows examined despite index existing
Auto-detectable: ✓ Yes mysql-explain pt-query-digest mysql-workbench
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Medium ✗ Manual fix Fix: Medium Context: File

✓ schema.org compliant