EXPLAIN执行计划是SQL优化的起点
写SQL不难,写高性能SQL需要理解执行计划。MySQL的EXPLAIN输出12列信息,其中type、key、rows、Extra四列是判断查询效率的核心依据。拿到慢查询日志中的SQL,第一步就是EXPLAIN查看执行计划,而不是急于改索引或加缓存。
执行计划中的每个行对应一次数据访问操作。多表关联查询会产生多行,顺序代表MySQL的执行顺序。理解这12列的含义,是数据库性能调优的基本功。
type列:访问类型决定性能基线
type列从最优到最差排序:system > const > eq_ref > ref > range > index > ALL。实际优化中,目标是至少达到ref级别,避免index和ALL。
const:主键或唯一索引精确匹配,最多返回一行。这是最高效的访问方式:
SELECT * FROM users WHERE id = 100;
eq_ref:关联查询中被驱动表通过主键或唯一索引关联,每次关联只取一行:
SELECT o.*, u.name FROM orders o JOIN users u ON o.user_id = u.id;
ref:非唯一索引等值匹配,可能返回多行。这是最常见的优化目标级别:
SELECT * FROM orders WHERE user_id = 100;
range:索引范围扫描,BETWEEN、>、< 等操作符触发:
SELECT * FROM orders WHERE create_time BETWEEN "2026-01-01" AND "2026-06-30";
index:全索引扫描,扫描整棵索引树但不回表。如果查询列正好是索引列,Extra会出现Using index(覆盖索引),性能尚可;否则需要回表,效率差。
ALL:全表扫描,无索引可用或优化器选择不走索引。这是必须解决的问题。
key与key_len:索引选择与使用长度
key列显示实际使用的索引名,key_len显示使用的索引字段总长度。联合索引只使用部分前缀时,key_len能直接反映使用到第几个字段。
举例,联合索引idx_abc建立在(a,b,c)上:
SELECT * FROM t WHERE a = 1 AND c = 3;
key_len只包含a字段的长度,说明c条件没有走索引。因为联合索引遵循最左前缀匹配,跳过b字段后c无法利用索引。
key_len的计算规则:INT占4字节,BIGINT占8字节,VARCHAR(N)在utf8mb4下占N*4+2字节,DATE占3字节,DATETIME占8字节。如果字段允许NULL,额外加1字节。
通过key_len反推使用了哪些索引字段,是判断联合索引是否生效的直接手段。
rows列与过滤比估算
rows列是MySQL预估需要扫描的行数,基于索引统计信息计算,不是精确值。但对于判断查询成本有参考意义。
一个重要的优化指标是filtered百分比,在EXPLAIN ANALYZE中展示。如果rows=10000但filtered=0.1%,说明预估扫描大量行但实际匹配极少,索引选择性差。
提升索引选择性的方法:对低选择性字段(如status只有0/1/2三个值)不要单独建索引,应和高选择性字段组合为联合索引,把高选择性字段放前面。
Extra列:隐藏的性能信号
Using index:覆盖索引,查询所需的所有列都在索引中,无需回表。这是最优的Extra信息。
Using where:存储引擎返回的数据在Server层进行过滤。如果同时出现Using index,说明先通过索引定位再在Server层过滤;如果没有Using index,说明回表后再过滤,效率较低。
Using temporary:使用临时表存储中间结果,常见于GROUP BY和ORDER BY没有使用索引的场景。这通常意味着需要优化。
Using filesort:排序操作无法利用索引完成,需要在内存或磁盘上额外排序。GROUP BY + ORDER BY组合且没有合适索引时经常出现。filesort不代表一定慢(小结果集在内存中排序很快),但大数据量下性能劣化明显。
Using index condition:索引下推(ICP),MySQL 5.6+的特性。把索引能过滤的条件在存储引擎层就执行,减少回表次数。这是正向信号。
实战案例:慢查询优化全过程
原始SQL,执行时间2.3秒:
SELECT * FROM orders
WHERE user_id = 100
AND status = 1
AND create_time > "2026-01-01"
ORDER BY amount DESC
LIMIT 20;
EXPLAIN结果:type=ref, key=idx_user_id, rows=50000, Extra=Using where; Using filesort。
问题分析:idx_user_id只覆盖user_id,status和create_time在Server层过滤,amount排序触发filesort。
建立联合索引:
ALTER TABLE orders ADD INDEX idx_user_status_time_amount (user_id, status, create_time, amount);
优化后EXPLAIN:type=ref, key=idx_user_status_time_amount, rows=200, Extra=Using index condition; Using index。执行时间降至12毫秒。
联合索引中amount放在最后,是因为它用于ORDER BY而非WHERE条件。这样索引既能过滤又能排序,避免了filesort。
EXPLAIN ANALYZE的增量信息
MySQL 8.0.18+支持EXPLAIN ANALYZE,输出实际执行时间和行数而非估算值:
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 100;
输出包含actual_time(实际耗时)、actual_rows(实际行数)、loops(循环次数)。将估算值和实际值对比,偏差过大说明统计信息过期,需要ANALYZE TABLE更新。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/mysql80-zhi-xing-ji-hua-shen-du-jie-xi-cong-type-lie-dao/