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是否为空——那说明优化器根本没考虑可用索引,得查是否写法触发了索引失效规则。
