EXPLAIN 输出中的 type 字段,是判断一条 SQL 性能最直接的指标。它代表 MySQL 决定用什么方式来访问表中的数据行,从最优到最差有着明确的等级。学会一眼看出 type 的好坏,是你优化 SQL 的第一步。
type 类型性能排序
按照访问效率从高到低,type 的常见取值可以这样排序:
const > eq_ref > ref > range > index > ALL
这不是死记硬背,搞清楚每个类型在数据库内部到底做了什么,你自然就能判断优劣。
const:常数级别的定值查找
“这一行数据我早就知道在哪儿了,直接读取。”
含义:使用主键或唯一索引进行等值匹配,最多只返回一条记录。这种查询在分析阶段就被优化器视为常数,速度是所有类型中最快的。
示例:SELECT * FROM user WHERE id = 100;,其中 id 是主键。
风险:基本没有。如果出现 const 以外的类型,检查你的 WHERE 条件是否真正命中主键或唯一索引。
eq_ref:唯一索引关联查找
“每次关联时,都能用被驱动表的唯一索引精确定位到一行。”
含义:通常出现在联表查询中,对于前表的每一行,从后表中通过唯一索引(主键或唯一键)等值匹配找到一行。性能极高,是联表查询中最理想的状态。
示例:SELECT * FROM orders o JOIN users u ON o.user_id = u.id; 如果 u.id 是主键,则 users 表的访问类型是 eq_ref。
风险:如果本应出现 eq_ref 的地方变成了 ref 或 ALL,检查关联条件是否使用了唯一索引,或者被驱动表的唯一键是否失效。
ref:普通索引等值查找
“用普通索引找到了一批数据,但每行都精准定位。”
含义:使用非唯一索引进行等值匹配,可能匹配到多行数据。这是通过索引进行精确查询的常见情况,性能依然很好。
示例:SELECT * FROM user WHERE name = '张三'; 其中 name 列有一个普通索引。如果该名字有多个用户,type 就是 ref。
风险:ref 本身是很好的类型,但你要关注它实际扫描的行数(rows 列)。如果 ref 返回几十万行,问题不在 type,而在索引选择性太低,可能要考虑联合索引或应用层限制。
range:索引范围扫描
“通过索引检索一个范围内的数据。”
含义:使用索引进行 =、<>、>、>=、<、<=、BETWEEN、IN 等范围条件检索。通常出现在 WHERE 条件中带有范围查询的时候。
示例:SELECT * FROM orders WHERE create_time BETWEEN '2024-01-01' AND '2024-01-31'; 如果 create_time 有索引,type 就是 range。
风险:range 本身是高效的,但要注意范围不能太大,否则扫描行数依然很多。另外,如果范围查询后面还有其他索引列,最左前缀匹配会在遇到第一个范围条件时中断,导致后面的列无法利用索引。这是索引设计中的高频问题。
index:索引全扫描
“虽然没有过滤条件,但我可以只遍历索引树,不用回表读全数据。”
含义:扫描整个索引树来获取数据。和 ALL(全表扫描)的区别是,index 只扫描索引,不需要访问表数据各行;但如果索引没有覆盖查询所需的全部列,依然可能回表。
示例:SELECT user_id FROM orders; 如果 user_id 有索引,可能进行 index 扫描。或者 SELECT * FROM orders WHERE status > 0; 也可能走 index。
风险:index 通常比 ALL 好,因为索引比表小,扫描成本更低。但如果查询返回大量行,甚至所有列,index 和 ALL 差别不大,且都无法利用索引过滤数据。如果 EXPLAIN 出现 index,请检查 WHERE 条件是否写了能过滤数据的条件。 索引覆盖扫描是好的,大量 index 全扫描则是需要优化的信号。
ALL:全表扫描
“没有合适的索引可用,必须从头到尾扫一遍表。”
含义:MySQL 会读取表中的每一行来找到匹配的数据,这是最差的一种访问类型。对于大表,ALL 可能是性能灾难。
示例:SELECT * FROM huge_table WHERE non_indexed_column = 'value'; 如果没有索引,就只能扫全表。
风险:生产环境中,对百万行以上的表做 ALL 扫描应被视为必须优化的问题。 如果你在 EXPLAIN 中看到 ALL,立刻思考:WHERE 条件中是否有字段忘了加索引?是否因为函数操作或隐式转换导致索引失效?是否子查询产生了派生表且该派生表没有索引?
其他类型补充说明
- system:比 const 更特殊,表只有一行数据(系统表),极少出现。
- index_merge:MySQL 同时使用多个索引并按交集或并集合并结果,有时是好选择,但也可能表示索引设计不合理,导致优化器需要用多个单列索引拼凑。
- ref_or_null:ref 的变体,额外查找 NULL 值。
Extra 字段中的风险信号
除了 type,Extra 字段经常藏着一颗颗“定时炸弹”,你应该特别留意:
- Using filesort:查询需要进行额外排序,无法利用索引顺序。如果你看到它,检查 ORDER BY 的列是否有合适的索引。
- Using temporary:查询需要用临时表来保存中间结果,常见于 GROUP BY 中排序与分组不一致,或 DISTINCT 与 ORDER BY 搭配不当。这是最需要避免的性能杀手之一。
- Using where:MySQL 在存储引擎返回行之后,在 Server 层再做一次过滤。如果它和 type=ALL 一起出现,说明索引过滤能力很弱。
- Using index:好消息,覆盖索引,查询所需的数据全部在索引中,无需回表。这是你优化时要追求的状态。
- Using index condition:使用了索引下推(ICP),在引擎层就尽量过滤行,减少回表。这是好现象。
- Using join buffer (Block Nested Loop / Hash Join):关联时没有用索引,不得不把驱动表的数据加载到 Join Buffer 中依次与被驱动表匹配。所有被驱动表的关联条件都应创建索引。 如果在这里看到 ALL,说明联表性能会很差。
实战记忆口诀
当你在 EXPLAIN 结果中快速扫描时,可以用这个经验法则:
- const / eq_ref:优秀,不动它。
- ref:正常,关注扫描行数。
- range:可接受,关注查询范围和索引中断。
- index:如果覆盖索引且行数不多则 OK;否则可优化。
- ALL 或 Extra 有 Using filesort / Using temporary:立即检查索引设计和 SQL 写法。
这张常用类型排序图,是你日常 SQL 优化中最高频的参照标准。遇到查询慢,先跑 EXPLAIN,看清 type 和 Extra,优化方向自然就清楚了。