MySQL基于代价估算自动选择全表扫描或索引扫描,当索引过滤性差、回表成本高、统计信息过期、索引列被函数操作或隐式转换时,会退化为全表扫描;应通过EXPLAIN分析代价,优先优化索引设计而非强制走索引。

MySQL 为什么有时走全表扫描,有时走索引扫描
MySQL 选哪种扫描方式,不是由 SQL 写法“决定”的,而是优化器基于 cost(代价估算)自动选择的。它会预估:走索引要读多少页 + 回表多少次 + 排序/临时表开销,对比全表扫描读多少页,取总代价更低的路径。所以即使你加了索引,EXPLAIN 仍可能显示 type: ALL(全表扫描)。
什么情况下 MySQL 会放弃索引,退化为全表扫描
常见诱因不是“索引坏了”,而是优化器判断走索引反而更慢:
-
WHERE条件匹配行数太多(比如status != 'done'覆盖 85% 的数据),索引过滤性差,回表成本高 - 查询列太多,且未被覆盖索引包含(
SELECT *或含非索引列),导致大量随机 I/O 回表 - 统计信息过期(
ANALYZE TABLE没更新),优化器误判索引选择率 - 使用函数或表达式操作索引列(如
WHERE YEAR(create_time) = 2024),导致索引失效(无法用 B+ 树快速定位) -
隐式类型转换(如
WHERE user_id = '123',而user_id是INT),触发全字段扫描
如何确认和干预扫描方式
先看执行计划,再针对性调整:
- 用
EXPLAIN FORMAT=JSON查看query_cost和used_columns,比普通EXPLAIN更清楚代价来源 - 强制走索引仅作诊断用:
SELECT ... FROM t USE INDEX (idx_name) WHERE ...,但上线禁用——掩盖问题而非解决 - 检查是否能转为覆盖索引:把
WHERE、ORDER BY、SELECT中用到的列都放进一个复合索引,避免回表(例如INDEX (a, b, c)支持WHERE a=1 ORDER BY b LIMIT 10) - 对低区分度字段(如
gender、is_deleted)单独建索引意义极小,应组合进复合索引并前置高区分列
EXPLAIN FORMAT=JSON SELECT id, name FROM users WHERE city = 'Beijing' AND age > 30;
容易被忽略的关键点
索引扫描 ≠ 快,全表扫描 ≠ 慢。InnoDB 全表扫描是顺序 I/O,而索引扫描+回表是大量随机 I/O;当数据量小(比如几千行)、缓冲池充足时,全表扫描可能更快。真正要盯的是 rows 预估是否严重偏离实际,以及 key 是否为空——那说明优化器根本没考虑可用索引,得查是否写法触发了索引失效规则。










