MySQL优化器选择聚簇索引扫描而非回表,是因为预估回表随机IO总成本(≥3~5次)高于顺序扫描聚簇索引几页的I/O开销,尤其当LIMIT 1且匹配行靠前时;统计信息Cardinality偏差、索引无id导致无法复用排序、SELECT *强制回表,均使其成本更高。

MySQL优化器怎么算“回表 vs 聚簇索引扫描”的成本
它不看“有没有索引”,而是估算:用idx_source_id查到主键后,要回表多少次?每次回表是随机IO;而走PRIMARY是顺序扫描聚簇索引页,每页能读多行。当预估回表次数 ≥ 3~5 次,且匹配行在聚簇索引靠前位置就能命中(比如LIMIT 1),优化器就倾向选主键扫描。
关键参数来自统计信息:Cardinality决定它认为source_id = '1814613774586351713'能筛出多少行;如果该值被低估(比如实际只有1行,但统计显示有2000行),它就会高估回表开销,放弃索引。
为什么idx_source_id无法跳过排序和回表
这个索引是(source_id, source_type, state),但查询里:
-
WHERE条件虽能命中前两列,但source_id是varchar(64),长度大、比较慢,且前缀重复率高,削弱了选择性 -
ORDER BY id ASC的id不在索引中,优化器无法保证按id顺序返回——它得先拿到所有匹配的id,再排序取第1个,或逐条比对 -
SELECT *要求所有字段,而索引只含source_id/source_type/state和主键id,必然触发回表
什么情况下全表扫描(实为聚簇索引扫描)反而更快
所谓“全表扫描”在InnoDB里本质是遍历PRIMARY索引叶子节点。它快的前提是:
- 目标行物理位置靠前(比如
id最小的匹配行就在前几页) - 过滤条件虽弱,但配合
LIMIT 1能让扫描提前终止 - 非聚簇索引的叶子节点分散存储,定位+回表的随机IO延迟 > 顺序读几页的耗时
- 表数据页大部分已在Buffer Pool缓存中,顺序扫描几乎不触发磁盘IO
强制走索引可能更慢,别硬加USE INDEX
即使你用USE INDEX (idx_source_id)强行指定,执行时间也可能从10秒变成30秒。因为优化器的判断基于当前统计和成本常数,不是拍脑袋——它已经权衡过I/O次数、CPU比较、内存排序开销。真正有效的干预是:
- 运行
ANALYZE TABLE mapping_filter_record更新统计信息 - 把
id加进索引尾部,改成(source_id, source_type, state, id),让ORDER BY id可复用索引顺序 - 若只查单行且
source_id唯一性强,考虑建唯一索引,让优化器明确知道最多1行,降低回表预期
最易被忽略的一点:优化器看到LIMIT 1,会优先找“最早能停下来的路径”,而不是“理论上最精确的路径”。顺序扫主键一旦命中就结束,而索引+回表必须做完全部步骤才能确认结果。


















