深入理解MySQL EXPLAIN工具与索引优化最佳实践

一、EXPLAIN 工具介绍

EXPLAIN 是 MySQL 中用于分析 SQL 查询性能的关键工具,它能模拟优化器执行 SQL 语句,帮助我们发现查询性能瓶颈。使用方法很简单:在 SELECT 语句前添加 EXPLAIN 关键字,MySQL 会返回执行计划信息,而不是执行该 SQL。

sql 复制代码
EXPLAIN SELECT * FROM table_name WHERE condition;

重要提示​:如果 FROM 子句中包含子查询,仍会执行该子查询并将结果放入临时表中。

二、EXPLAIN 的变种

1. EXPLAIN EXTENDED

在 EXPLAIN 基础上额外提供查询优化信息。执行后可通过 SHOW WARNINGS 查看优化后的查询语句,了解优化器做了哪些优化。

sql 复制代码
EXPLAIN EXTENDED SELECT * FROM film WHERE id = 1;
SHOW WARNINGS;

新增字段​:

  • filtered:百分比值,表示将要和前一个表进行连接的行数估算(rows * filtered/100)

2. EXPLAIN PARTITIONS

相比 EXPLAIN 多了一个 partitions 字段,如果查询是基于分区表,会显示查询将访问的分区。

三、EXPLAIN 输出列详解

1. id 列

  • 表示 SELECT 的序列号,有几个 SELECT 就有几个 id
  • id 越大执行优先级越高,id 相同则从上往下执行,id 为 NULL 最后执行

2. select_type 列

表示查询类型:

类型 说明
SIMPLE 简单查询,不包含子查询和 UNION
PRIMARY 复杂查询中最外层的 SELECT
SUBQUERY 包含在 SELECT 中的子查询(不在 FROM 子句中)
DERIVED 包含在 FROM 子句中的子查询,MySQL 会将结果存放在临时表中
UNION 在 UNION 中的第二个和随后的 SELECT

3. table 列

表示当前 EXPLAIN 行正在访问的表。当 FROM 子句中有子查询时,table 列是 <derivedN> 格式,表示依赖 id=N 的查询。

4. type 列

表示关联类型或访问类型,决定 MySQL 如何查找表中的行。从最优到最差依次为:

system > const > eq_ref > ref > range > index > ALL

  • NULL :优化阶段分解查询,执行阶段无需访问表或索引

    sql 复制代码
    EXPLAIN SELECT MIN(id) FROM film;
  • const, system :查询某部分被优化为常量,使用主键或唯一索引

    sql 复制代码
    EXPLAIN SELECT * FROM (SELECT * FROM film WHERE id = 1) tmp;
  • eq_ref :主键或唯一索引的所有部分被连接使用,最多返回一条记录

    sql 复制代码
    EXPLAIN SELECT * FROM film_actor LEFT JOIN film ON film_actor.film_id = film.id;
  • ref :使用普通索引或唯一索引的部分前缀,可能找到多条记录

    sql 复制代码
    -- 简单查询
    EXPLAIN SELECT * FROM film WHERE name = 'film1';
    
    -- 关联查询
    EXPLAIN SELECT film_id FROM film LEFT JOIN film_actor ON film.id = film_actor.film_id;
  • range :范围扫描,通常出现在 IN(), BETWEEN, >, < 等操作中

    sql 复制代码
    EXPLAIN SELECT * FROM actor WHERE id > 1;
  • index :扫描全索引,一般为覆盖索引

    sql 复制代码
    EXPLAIN SELECT * FROM film;
  • ALL :全表扫描,最差的访问类型

    sql 复制代码
    EXPLAIN SELECT * FROM actor;

5. possible_keys 列

显示查询可能使用哪些索引来查找。

6. key 列

显示 MySQL 实际采用哪个索引来优化查询。如果没有使用索引,则为 NULL。

7. key_len 列

显示 MySQL 在索引中使用的字节数,通过该值可推断使用了索引的哪些列。

计算规则​:

  • 字符串:char(n)和 varchar(n),n 代表字符数(UTF-8 下,数字/字母 1 字节,汉字 3 字节)
    • char(n):如果存汉字,长度为 3n 字节
    • varchar(n):如果存汉字,长度为 3n+2 字节(2 字节存储长度)
  • 数值类型:tinyint(1)、smallint(2)、int(4)、bigint(8)
  • 时间类型:date(3)、timestamp(4)、datetime(8)
  • 允许 NULL 的字段:额外 1 字节

8. ref 列

显示在 key 列记录的索引中,表查找值所用到的列或常量,如 const(常量)、字段名等。

9. rows 列

MySQL 估计要读取并检测的行数(不是结果集行数)。

10. filtered 列

百分比值,rows * filtered/100 可估算出将要和前一个表进行连接的行数。

11. Extra 列

展示额外信息,常见重要值:

说明
Using index 使用覆盖索引,查询字段都可从索引中获取,不需要回表
Using where 使用 WHERE 语句处理结果,查询列未被索引覆盖
Using index condition 查询列不完全被索引覆盖,WHERE 条件中是前导列的范围
Using temporary 创建临时表处理查询,需要优化
Using filesort 外部排序,数据量大时在磁盘排序,需要优化
Select tables optimized away 使用聚合函数(如 MIN、MAX)访问存在索引的字段

四、索引优化最佳实践

1. 全值匹配

sql 复制代码
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei';
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' AND age = 22;
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' AND age = 22 AND position = 'manager';

2. 最左前缀法则

索引多列时,必须从索引最左前列开始且不跳过索引中的列。

sql 复制代码
EXPLAIN SELECT * FROM employees WHERE name = 'Bill' AND age = 31; -- 有效
EXPLAIN SELECT * FROM employees WHERE age = 30 AND position = 'dev'; -- 无效
EXPLAIN SELECT * FROM employees WHERE position = 'manager'; -- 无效

MySQL 8.0 新特性:索引跳跃扫描(Index Skip Scan)

  • 在特定条件下,即使不使用最左前缀,MySQL 也能使用索引
  • 限制条件:
    1. 查询仅依赖一张表
    2. 不能使用 GROUP BY 或 DISTINCT
    3. 查询字段必须是索引中的列

3. 不在索引列上做任何操作

sql 复制代码
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei'; -- 有效
EXPLAIN SELECT * FROM employees WHERE LEFT(name, 3) = 'LiLei'; -- 无效

日期处理优化​:

sql 复制代码
-- 无效
EXPLAIN SELECT * FROM employees WHERE DATE(hire_time) = '2018-09-30';

-- 有效
EXPLAIN SELECT * FROM employees WHERE hire_time >= '2018-09-30 00:00:00' AND hire_time <= '2018-09-30 23:59:59';

4. 存储引擎不能使用范围条件右边的列

sql 复制代码
-- 有效
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' AND age = 22 AND position = 'manager';

-- 无效(age为范围条件,position无法使用索引)
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' AND age > 22 AND position = 'manager';

5. 尽量使用覆盖索引

sql 复制代码
-- 有效(使用覆盖索引)
EXPLAIN SELECT name, age FROM employees WHERE name = 'LiLei' AND age = 23 AND position = 'manager';

-- 无效(需要回表获取其他字段)
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' AND age = 23 AND position = 'manager';

6. 避免使用不等于(!=, <>)和 NOT IN

sql 复制代码
EXPLAIN SELECT * FROM employees WHERE name != 'LiLei'; -- 无法使用索引

7. IS NULL 和 IS NOT NULL 通常无法使用索引

sql 复制代码
EXPLAIN SELECT * FROM employees WHERE name IS NULL; -- 通常无法使用索引

8. LIKE 查询优化

  • LIKE 'KK%':相当于等值查询,可以使用索引
  • LIKE '%KK'LIKE '%KK%':相当于范围查询,无法使用索引

解决方案​:

sql 复制代码
-- 使用覆盖索引
EXPLAIN SELECT name, age, position FROM employees WHERE name LIKE '%Lei%';

-- 无法使用覆盖索引时,考虑使用搜索引擎

9. 字符串必须加单引号

sql 复制代码
EXPLAIN SELECT * FROM employees WHERE name = '1000'; -- 有效
EXPLAIN SELECT * FROM employees WHERE name = 1000; -- 无效(字符串未加单引号)

10. 少用 OR 或 IN

sql 复制代码
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' OR name = 'HanMeimei'; -- 可能不使用索引

11. 范围查询优化

sql 复制代码
-- 无效(MySQL优化器可能选择不走索引)
EXPLAIN SELECT * FROM employees WHERE age >= 1 AND age <= 2000;

-- 有效(拆分范围)
EXPLAIN SELECT * FROM employees WHERE age >= 1 AND age <= 1000;
EXPLAIN SELECT * FROM employees WHERE age >= 1001 AND age <= 2000;

五、MySQL 5.7 与 8.0 的差异

  1. EXPLAIN EXTENDED​:

    • MySQL 5.7:需要使用 EXPLAIN EXTENDEDSHOW WARNINGS
    • MySQL 8.0:已废除 EXPLAIN EXTENDED,只需使用 EXPLAIN 即可
  2. 索引跳跃扫描​:

    • MySQL 8.0 引入了索引跳跃扫描,使某些不遵循最左前缀的查询也能使用索引
    • MySQL 5.7 不支持索引跳跃扫描
  3. SQL 模式​:

    • MySQL 8.0 默认开启 ONLY_FULL_GROUP_BY,可能需要调整
    sql 复制代码
    -- 关闭ONLY_FULL_GROUP_BY
    SET sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));

六、总结

  1. EXPLAIN 是优化 SQL 的利器,通过分析执行计划可以发现性能瓶颈。
  2. 索引设计需遵循最左前缀原则,但 MySQL 8.0 引入了索引跳跃扫描,提供了一些灵活性。
  3. 避免在索引列上做任何操作(计算、函数、类型转换),否则索引失效。
  4. 使用覆盖索引可以减少回表操作,提高查询效率。
  5. 范围查询需要谨慎处理,避免使用大范围查询导致索引失效。
  6. MySQL 8.0 在索引优化方面有显著改进,但设计索引时仍应遵循最佳实践。

记住​:索引不是越多越好,而是越"准"越好。合理设计索引,可以大幅提升数据库查询性能,为应用带来更好的用户体验。

相关推荐
小夏卷编程2 小时前
mysql 5.6.50,5.7 版本 索引碎片化导致服务器cpu骤增问题
数据库·mysql
a程序小傲11 小时前
京东Java面试被问:动态规划的状态压缩和优化技巧
java·开发语言·mysql·算法·adb·postgresql·深度优先
舰长11513 小时前
linux安装mysql8.0.43 最新版本
adb
007php00713 小时前
mySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据
数据库·redis·git·mysql·面试·职场和发展·php
lkbhua莱克瓦2413 小时前
进阶-存储过程3-存储函数
java·数据库·sql·mysql·数据库优化·视图
碎像14 小时前
10分钟搞定 MySQL 通过Binlog 数据备份和恢复
数据库·mysql
岁岁种桃花儿15 小时前
MySQL 8.0 基本数据类型全面解析
数据库·mysql·数据库开发
大霞上仙17 小时前
adb 远程连接设备
adb
chuxinweihui17 小时前
MySQL数据库基础
数据库·mysql