✏️ Explanatory Question

You added the perfect index but EXPLAIN still shows a full table scan — why does MySQL ignore an index?

👁 10 Views
📘 Detailed Answer
🟢 Easy
💡

Answer with Explanation

88

You added the perfect index but MySQL still does a full scan — why does it ignore an index?

Level: Very Hard — Tests deep optimizer knowledge; there are many reasons, and "cost-based decision" is only one.

Scenario: You created an index on orders(status) exactly matching your WHERE status = 'completed'. But EXPLAIN still shows type=ALL, key=NULL — a full table scan. The index is definitely there (SHOW INDEX confirms it). Give me the possible reasons.

Sample Data Context — orders table (10M rows)

status valuerow count% of table
'completed'9,500,00095%
'pending'400,0004%
'cancelled'100,0001%

The main cause here: 'completed' matches 95% of the table. The optimizer calculates that using the index (random I/O for 9.5M lookups + reading the rows) is more expensive than just scanning the whole table sequentially. So it correctly chooses a full scan. Low selectivity = index ignored.

Why interviewers ask this: They want to know you understand MySQL is a cost-based optimizer — "index exists" ≠ "index used." A strong candidate lists multiple distinct reasons, not just one.

The 6 Reasons MySQL Ignores an Index

#ReasonFix
1 Low selectivity — condition matches a large % of rows Expected behaviour; scan is genuinely cheaper
2 Function on the columnWHERE YEAR(created_at)=... Rewrite as a sargable range
3 Implicit type conversionWHERE phone = 123 (phone is VARCHAR) Match types: phone = '123'
4 Leading wildcardLIKE '%text' Use 'text%' or a FULLTEXT index
5 Stale statistics — optimizer has outdated cardinality ANALYZE TABLE
6 OR across different columns / not a leftmost prefix of a composite index Rewrite with UNION or reorder index

Demonstrating & Fixing Each Cause

-- REASON 1: low selectivity -> optimizer skips index (correctly)
EXPLAIN SELECT * FROM orders WHERE status = 'completed';   -- ALL (95% match)
EXPLAIN SELECT * FROM orders WHERE status = 'cancelled';   -- ref (1% match, uses index)

-- REASON 2: function kills the index
-- BAD:
SELECT * FROM orders WHERE YEAR(created_at) = 2026;
-- GOOD:
SELECT * FROM orders WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01';

-- REASON 3: implicit conversion (phone is VARCHAR)
SELECT * FROM users WHERE phone = 9876543210;    -- BAD: no index, per-row cast
SELECT * FROM users WHERE phone = '9876543210';  -- GOOD: uses index

-- REASON 5: stale stats -> optimizer misjudges; refresh them
ANALYZE TABLE orders;

-- FORCING an index (use sparingly, as a last resort / to test)
SELECT * FROM orders FORCE INDEX (idx_status)
WHERE status = 'completed';
On FORCE INDEX: It's a diagnostic tool, not a permanent fix. If the optimizer avoids an index, it usually has a good reason. Forcing it can make things slower. Use it to test a hypothesis, then address the real cause.
Interviewer follow-up: "For the 95%-match case, how would you make it fast anyway?" → A covering index that includes all selected columns lets MySQL answer from the index alone (index scan instead of table scan), or better — partition the table or query the minority statuses instead.