explain显示用了索引但查询更慢,是因为优化器虽选中索引,但低选择性导致回表开销巨大:如gender='男'返回50%行,需50万次随机i/o回表,远慢于顺序全表扫描;此时应避免在低选择性字段单独建索引,并通过analyze table或直方图更新统计信息以改善优化器决策。

EXPLAIN 显示用了索引但查询更慢,为什么?
不是索引“失效”,而是优化器选了它,但执行代价更高。典型现象:EXPLAIN 的 type 是 ref 或 range,Extra 里没出现 Using index,但查询耗时反而比全表扫描长。
根本原因在于回表开销:性别字段只有两个值,假设查 gender = '男' 返回 50% 行,MySQL 得先扫一遍索引页拿到 50 万个主键,再逐个回聚簇索引取完整行——50 万次随机 I/O,远慢于一次顺序全表扫描。
- 用
SELECT COUNT(*) FROM table WHERE gender = '男'测,如果耗时显著高于SELECT COUNT(*) FROM table,基本可确认是回表拖慢 -
SELECT gender FROM table WHERE gender = '男'可能快,因为走覆盖索引(Extra出现Using index),但这种查询本身意义有限 - MySQL 5.7+ 和 8.0 对低选择性索引更激进跳过,但若统计信息陈旧(
Cardinality没更新),仍可能误选
SHOW INDEX 看到的 Cardinality 为什么不准?
Cardinality 是采样估算值,不是实时精确计数。比如百万级表上性别字段,SHOW INDEX FROM users 显示 Cardinality 是 2,这没错;但若实际数据倾斜(99% 是女性),而采样恰好没抽到那 1%,优化器仍会按“均匀分布”估算,导致决策偏差。
- 运行
ANALYZE TABLE users可触发重新采样,但对大表有锁、耗资源,别当常规操作 - MySQL 8.0 支持直方图(
ANALYZE TABLE users UPDATE HISTOGRAM ON gender),能更好反映真实分布,但默认不启用 - 别依赖
Cardinality判断是否建索引,直接算选择性:SELECT COUNT(DISTINCT gender) / COUNT(*) FROM users,结果
status/gender/type 这类字段什么时候能用上索引?
单独建索引几乎没意义,但嵌入组合索引后可能生效——前提是高基数字段在前,且查询条件能用上最左前缀。
- 正确写法:
ALTER TABLE orders ADD INDEX idx_user_status (user_id, status),然后查WHERE user_id = 123 AND status = 'paid' - 错误写法:
INDEX (status, user_id),查WHERE status = 'paid'时,B+ 树第一层只有两个分支,后续user_id完全无序,无法利用范围或等值高效过滤 - 覆盖场景有用:如果查询只涉及组合索引包含的列,如
SELECT status FROM orders WHERE user_id = 123 AND status = 'paid',即使status基数低,也能避免回表 - 极端倾斜数据例外:若
status = 'processing'仅占 0.001%,且业务高频查它,索引可能被选中,但这是特例,不能推广
ENUM('男','女') 能提升索引效果吗?
不能提升选择性,但能略微改善存储和比较效率——对索引是否被选用影响极小,属于边际优化。
-
ENUM存储为整数(内部映射),比VARCHAR占空间小、比较快,索引体积略小,间接可能让优化器在临界点倾向使用索引,但不改变本质问题 - 别指望靠换类型解决低基数问题,
COUNT(DISTINCT gender)还是 2,选择性仍是 ~0.5 - 真正要改,得重构字段语义:比如把
status拆成status_v2+ 时间戳前缀,或引入业务维度(region+status)人为拉高区分度
INSERT/UPDATE/DELETE 都要同步维护这份几乎没人用的索引,长期看,对写入吞吐和磁盘 I/O 的损耗,比那点虚幻的查询收益实在得多。











