扫描行数过大本质是索引未用好或设计不合理,核心在回表次数多和过滤率低;应减少回表、提升筛选效率,如用覆盖索引、避免函数操作和隐式转换。

扫描行数过大,本质是数据库没用好索引,或索引设计不合理。核心问题往往出在两个地方:回表次数多、过滤率低。优化方向很明确——减少回表、提升单次查询的筛选效率。
为什么回表会导致扫描行数暴增?
当查询语句中用了非覆盖索引(比如只在 status 字段建了索引,但 SELECT 要取 id, name, create_time),MySQL 会先通过索引找到满足条件的主键值,再回到聚簇索引(主键索引)里逐条查出完整记录——这个过程叫回表。如果索引过滤性差(比如 status = 'pending' 匹配 10 万行),就要回表 10 万次,IO 和 CPU 开销都大。
- 避免 SELECT *,只查真正需要的字段
- 把高频查询字段加到联合索引末尾,构成覆盖索引(如 (status, name, create_time))
- 注意最左前缀原则:WHERE 条件必须包含联合索引的左侧连续列,才能有效利用索引下推(ICP)提前过滤
过滤率低,索引就形同虚设
即使有索引,如果 WHERE 条件匹配太多行(比如性别字段只有男/女,加索引几乎无效),优化器可能直接放弃走索引,改用全表扫描;或者走了索引,却要回表大量数据,最终效果还不如不走。
- 用 EXPLAIN 看 rows 和 filtered 值:filtered 值越低(比如
- 低基数字段(如 state、is_deleted)单独建索引意义不大,应组合进高频查询的联合索引中,并放在右侧
- 对范围查询(>, LIKE 'abc%')等,确保它们在联合索引中处于“可中断”位置(即后面不再参与索引查找)
实战中容易被忽略的关键点
有些写法看着合理,实则破坏索引有效性:
- 在 WHERE 中对索引字段做函数操作:WHERE YEAR(create_time) = 2024 → 改成 create_time >= '2024-01-01' AND create_time
- 隐式类型转换:user_id 是 VARCHAR,但传入数字 123 → MySQL 自动转类型,导致索引失效
- OR 条件混用不同字段:WHERE a = 1 OR b = 2,除非 a、b 都有独立索引且优化器选择合并,否则大概率全表扫
- ORDER BY 和 LIMIT 配合不当:没走索引排序时,MySQL 可能先取出全部结果再排序分页,极大放大扫描量

















