MySQL优化器选全表扫描,是因为估算走唯一索引成本更高:可能因过滤性差、最左前缀不满足、函数/隐式转换失效、回表代价超顺序读、排序无法复用索引、统计信息失真等原因,导致type=ALL时key字段仅为误导。

MySQL优化器选全表扫描,不是因为找不到唯一索引,而是它算出来——走那个唯一索引反而更贵。
EXPLAIN 显示 type=ALL 但 key 有唯一索引名?别信表象
看到 key 字段非空(比如显示 uk_source_id),不代表真用了;要看 type 是不是 ALL 或 index。如果 type=ALL,说明优化器压根没把该索引纳入最终计划,key 值只是历史残留或格式误导。真正生效的索引必须同时满足:type 是 const、eq_ref、ref 或 range,且 key 对应你期望的索引名。
-
possible_keys列出所有“能用”的索引,key只显示“这次选的”,但type=ALL时这个key实际无效 - 唯一索引列上写
WHERE col IS NULL或WHERE col != 'x',优化器可能判定过滤性差,直接跳过 - 联合唯一索引
UNIQUE(a, b),只查WHERE b = ?,最左前缀不满足,索引不可检索,优化器连试都不试
唯一索引列被函数/隐式转换一碰就失效
唯一性再高,也扛不住 WHERE 条件里一次函数调用或类型错配。B+ 树无法做范围定位,优化器只能放弃索引走全表。
-
WHERE UNIX_TIMESTAMP(create_time) = 1728000000→ 改成create_time = FROM_UNIXTIME(1728000000) -
WHERE user_id = '12345'但user_id是BIGINT→ 改成WHERE user_id = 12345,否则触发隐式转换 -
WHERE source_id LIKE '%abc'(前置通配符)→ 索引无法定位起始位置,等价于全扫
回表代价太高,优化器宁愿顺序读主键
哪怕唯一索引能精确定位 1 行,只要 SELECT 的字段不在索引里,就得回表取数据。而 InnoDB 主键是聚簇索引,顺序遍历本身就是最廉价的 I/O。当优化器估算“回表 1 次的随机读成本 > 扫 1 页主键的顺序读”时,它就敢选全表。
-
SELECT *+ 唯一索引 → 几乎必回表,尤其大宽表,rows小但query_cost高 - 唯一索引是
UNIQUE(source_id),但语句带ORDER BY update_time→ 无法复用索引有序性,额外排序 + 回表,总成本爆表 - 表缓存差、buffer pool 小,随机 I/O 延迟放大,进一步拉高回表权重
CARDINALITY 失真让唯一索引“看起来像普通列”
唯一索引的 CARDINALITY 理论应 ≈ 表总行数。但如果统计信息过期(比如大批量 INSERT/DELETE 后未 ANALYZE),CARDINALITY 显示为 1000,而实际有 500 万行,优化器会误判“这列重复值极多”,直接排除该索引。
- 查真实值:
SHOW INDEX FROM mapping_filter_record WHERE Key_name = 'uk_source_id',对比Cardinality和TABLE_ROWS - 强制刷新:
ANALYZE TABLE mapping_filter_record(注意锁表现象,生产慎用) - 长期解法:
SET GLOBAL innodb_stats_persistent = ON+innodb_stats_auto_recalc = ON
真正难缠的不是“没建索引”,而是“建了却算不准”——统计失真、回表开销浮动、参数值分布突变,都会让同一条 SQL 在不同时间点走向完全不同执行路径。盯死 EXPLAIN FORMAT=JSON 里的 query_cost 和 range_analysis 段,比背口诀管用十倍。


















