是,优化器因区分度低主动放弃索引;确认方法为explain中type=all或key=null且rows≈总行数、extra无using index,再验证selectivity<0.01。

会,而且非常常见——不是“可能”,而是优化器算完成本后主动放弃索引。 区分度低本身不直接触发全表扫描,但它让优化器估算出「走索引 + 回表」的总开销高于顺序读主键页,于是选 type=ALL。
怎么确认是区分度低导致没走索引
别猜,先看 EXPLAIN 输出里最硬的两个信号:
-
type=ALL或key=NULL,同时rows接近表总行数 -
Extra里没有Using index,且查询用了SELECT *或非索引列
再交叉验证:如果建了 INDEX idx_status (status),但查 WHERE status = 'processing' 仍全表扫,大概率就是它。此时别急着删索引,先算真实区分度:
SELECT COUNT(DISTINCT status) / COUNT(*) AS selectivity FROM orders;
结果低于 0.01(即 1%),基本可以判定该索引对单列查询无效。
CARDINALITY 不可靠,必须手动刷新和比对
CARDINALITY 是采样估算值,大表上极易失真。比如你肉眼知道 state 有 5 种值,但 SHOW INDEX 显示 CARDINALITY = 2,说明统计已过期。
强制更新并查真实基数:
ANALYZE TABLE orders;<br>SELECT COLUMN_NAME, CARDINALITY, INDEX_NAME<br>FROM information_schema.STATISTICS<br>WHERE TABLE_NAME = 'orders' AND INDEX_NAME = 'idx_status';
如果 CARDINALITY / 表总行数 ,这个索引在单列查询中大概率被跳过。
低区分度字段不是不能用,而是不能“单建”或“放错位置”
单独为 is_deleted 建索引,99% 的场景下只是白占空间、拖慢写入。但它在联合索引里可能很关键——前提是位置对、前缀列够强:
- ✅ 有效:索引
idx_user_id_is_deleted (user_id, is_deleted),查WHERE user_id = 123 AND is_deleted = 0 - ❌ 无效:索引
idx_is_deleted_user_id (is_deleted, user_id),只查user_id = 123就无法命中最左前缀 - ⚠️ 风险:即使联合索引生效,若
user_id本身区分度也低(比如大量用户共用同一租户 ID),整个索引效率仍会断崖下跌
真正起效的前提,是前导列能快速收敛到极小数据集,后缀列才来“微调”。否则,就是用一个低效索引模拟全表扫描。
FORCE INDEX 是障眼法,不是解法
加 FORCE INDEX(idx_status) 后 EXPLAIN 显示走索引了,但实际执行可能更慢——因为绕过了成本判断,硬拉一条高随机 I/O 路径。你看到的是“走了索引”,不是“变快了”。
实操中更值得做的是:
- 删掉单列低区分度索引,减少写开销和存储浪费
- 把该字段作为联合索引后缀,并确保前导列区分度 ≥ 0.1
- 若查询只取该字段(如
SELECT is_deleted FROM orders),可接受type=index,避免回表
最容易被忽略的一点:线上加联合索引时,is_deleted 放第几位,直接影响是否锁表、是否生效、是否被后续所有同类查询复用——顺序错了,等于白建。











