mysql执行过程中如何选择扫描方法(全表扫描、索引扫描)

10次阅读

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

mysql执行过程中如何选择扫描方法(全表扫描、索引扫描)

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_idint),触发全字段扫描

如何确认和干预扫描方式

先看执行计划,再针对性调整:

  • EXPLAIN format=jsON 查看 query_costused_columns,比普通 EXPLAIN 更清楚代价来源
  • 强制走索引仅作诊断用:SELECT ... FROM t USE INDEX (idx_name) WHERE ...,但上线禁用——掩盖问题而非解决
  • 检查是否能转为覆盖索引:把 WHEREORDER BYSELECT 中用到的列都放进一个复合索引,避免回表(例如 INDEX (a, b, c) 支持 WHERE a=1 ORDER BY b LIMIT 10
  • 对低区分度字段(如 genderis_deleted)单独建索引意义极小,应组合进复合索引并前置高区分列
EXPLAIN FORMAT=JSON SELECT id, name FROM users WHERE city = 'Beijing' AND age > 30;

容易被忽略的关键点

索引扫描 ≠ 快,全表扫描 ≠ 慢。InnoDB 全表扫描是顺序 I/O,而索引扫描+回表是大量随机 I/O;当数据量小(比如几千行)、缓冲池充足时,全表扫描可能更快。真正要盯的是 rows 预估是否严重偏离实际,以及 key 是否为空——那说明优化器根本没考虑可用索引,得查是否写法触发了索引失效规则。

text=ZqhQzanResources