当前位置:首页 > 技术 > 正文内容

MySQL执行计划分析指南

访客 技术 2026年5月26日 3

一、概述

MySQL的执行计划(EXPLAIN)是一个强大的工具,用于分析查询语句的执行方式和性能特征。通过执行计划,我们可以了解MySQL如何处理SQL查询,从而识别并解决性能瓶颈。

官方文档链接:

执行计划提供的关键信息包括:

  • 查询中表的读取顺序
  • 数据访问操作类型
  • 可用的索引
  • 实际使用的索引
  • 表之间的关联关系
  • 优化器预估需要扫描的行数

版本演进

  • MySQL 5.6.3之前,EXPLAIN仅支持SELECT语句;5.6.3之后扩展到支持UPDATE和DELETE语句
  • 在MySQL 5.7之前,需要使用EXPLAIN PARTITIONS显示分区信息,使用EXPLAIN EXTENDED显示过滤信息;5.7版本后,EXPLAIN默认显示这些信息

基本语法

EXPLAIN SELECT select_options

DESCRIBE SELECT select_options

环境准备:

CREATE DATABASE performance_test CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE performance_test;
CREATE TABLE employees(id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100));
CREATE TABLE departments(id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100));
CREATE TABLE projects(id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100));
CREATE TABLE assignments(id INT PRIMARY KEY AUTO_INCREMENT, employee_id INT, project_id INT);

INSERT INTO employees(name) VALUES('Alice'),('Bob'),('Charlie'),('David'),('Eve');
INSERT INTO departments(name) VALUES('HR'),('IT'),('Finance'),('Marketing');
INSERT INTO projects(name) VALUES('Project A'),('Project B'),('Project C'),('Project D');
INSERT INTO assignments(employee_id, project_id) VALUES(1,1),(2,2),(3,3),(4,4),(1,2);

二、基本用法

执行计划的使用方法很简单:在SELECT语句前加上EXPLAIN或DESC关键字,即可获取该查询的执行计划信息。

EXPLAIN SELECT * FROM employees, departments WHERE employees.id = 1;
DESC SELECT * FROM employees, departments WHERE employees.id = 1;
字段 说明
id 查询标识符,表示SELECT操作的顺序
select_type 查询类型,如SIMPLE、PRIMARY、UNION等
table 正在访问的表名
partitions 匹配的分区信息,非分区表为NULL
type 访问类型,从最佳到最差排序:system→const→eq_ref→ref→range→index→ALL
possible_keys 可能使用的索引列表
key 实际使用的索引
key_len 使用的索引字节数
ref 索引列与哪个列或常量进行比较
rows 预估需要扫描的行数
filtered 根据查询条件过滤的行百分比
Extra 额外信息,如是否使用临时表、文件排序等

三、字段详解

3.1、id字段

id表示查询标识符,决定了SELECT操作的执行顺序。

  • 当id值相同时,执行顺序从上到下
EXPLAIN SELECT * FROM employees e, departments d, projects p WHERE e.id = d.id AND d.id = p.id;
  • 当id值不同时,表示存在子查询,id值越大,优先级越高,越先执行
EXPLAIN
SELECT *
FROM departments
WHERE id = (SELECT id
            FROM employees
            WHERE id = (SELECT project_id
                        FROM projects
                        WHERE name = 'Project A'));

3.2、select_type与table字段

select_type表示查询的类型,用于区分普通查询、联合查询、子查询等复杂查询场景。

  • SIMPLE:简单的SELECT查询,不包含子查询或UNION
EXPLAIN SELECT * FROM employees;
  • PRIMARY:最外层查询,包含复杂子部分
EXPLAIN
SELECT *
FROM departments
WHERE id = (SELECT id
            FROM employees
            WHERE id = (SELECT project_id
                        FROM projects
                        WHERE name = 'Project B'));
  • SUBQUERY:在SELECT或WHERE列表中包含的子查询
EXPLAIN
SELECT *
FROM departments
WHERE id = (SELECT id
               FROM employees
               WHERE name = 'Alice');
  • DERIVED:FROM列表中的子查询,MySQL会递归执行并放入临时表
  • UNION:UNION中的第二个或后续SELECT
  • UNION RESULT:UNION的结果集
EXPLAIN
SELECT *
FROM employees
UNION
SELECT *
FROM departments;

3.3、partitions字段

partitions显示查询匹配到的分区信息,对于非分区表值为NULL。

对于非分区表:

EXPLAIN SELECT * FROM employees WHERE id = 1;

创建分区表示例:

CREATE TABLE employees_partitioned (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100)
) 
PARTITION BY RANGE (id) (
    PARTITION p0 VALUES LESS THAN (2),
    PARTITION p1 VALUES LESS THAN (4),
    PARTITION p2 VALUES LESS THAN (6)
);

INSERT INTO employees_partitioned(name) VALUES('Alice'),('Bob'),('Charlie'),('David'),('Eve');

查询分区表时,partitions字段会显示命中分区:

EXPLAIN SELECT * FROM employees_partitioned WHERE id = 1;
EXPLAIN SELECT * FROM employees_partitioned WHERE id BETWEEN 1 AND 5;

3.4、type字段

type表示表的访问类型,是性能分析的重要指标。

访问类型从最佳到最差的排序:

system > const > eq_ref > ref > range > index > ALL
  • SYSTEM:表仅有一行(系统表),极少出现
  • CONST:通过索引一次找到,用于比较主键或唯一索引
EXPLAIN SELECT * FROM employees WHERE id = 1;
  • EQ_REF:唯一性索引扫描,每个索引键只匹配一行
EXPLAIN SELECT * FROM employees e, departments d WHERE e.id = d.id;
  • REF:非唯一性索引扫描,返回匹配某个单独值的所有行
-- 无索引
EXPLAIN SELECT * FROM employees, departments WHERE employees.name = departments.name;
-- 添加索引后
CREATE INDEX idx_name ON departments(name);
EXPLAIN SELECT * FROM employees, departments WHERE employees.name = departments.name;
  • RANGE:检索给定范围的行,使用索引选择行
EXPLAIN SELECT * FROM employees WHERE id > 2;
EXPLAIN SELECT * FROM employees WHERE id IN (1,3);
  • INDEX:使用了索引,但没有通过索引进行过滤,通常用于排序或分组
EXPLAIN SELECT * FROM employees ORDER BY id;
  • ALL:完整表扫描,性能最差
EXPLAIN SELECT * FROM employees;

3.5、possible_keys与key字段

  • possible_keys:可能应用到表上的索引列表,不一定实际使用
  • key:实际使用的索引,NULL表示未使用索引
  1. 理论上没有使用索引,但实际上使用了(覆盖索引)
EXPLAIN SELECT id FROM employees;
  1. 理论和实际上都没有使用索引
EXPLAIN SELECT * FROM employees WHERE name = 'Alice';
  1. 理论和实际上都使用了索引
EXPLAIN SELECT * FROM departments WHERE name = 'HR';

3.6、key_len字段

key_len表示索引中使用的字节数,用于判断索引使用是否充分。

不同数据类型的key_len计算:

列类型 是否为空 长度 key_len
tinyint 允许NULL 1 1 + 1
tinyint not null 不允许NULL 1 1
int 允许NULL 4 4 + 1
int not null 不允许NULL 4 4
char(10) 允许NULL utf8=3 10*3 + 2 + 1
char(10) not null 不允许NULL utf8=3 10*3 + 2
varchar(20) 允许NULL utf8=3 20*3 + 2 + 1

示例:

CREATE TABLE user_profile (
  id bigint NOT NULL AUTO_INCREMENT,
  name varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  age int NULL DEFAULT NULL,
  sex char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (id) USING BTREE,
  INDEX idx_name(name) USING BTREE,
  INDEX idx_age(age) USING BTREE
);
EXPLAIN SELECT * FROM user_profile WHERE id = 1;
EXPLAIN SELECT * FROM user_profile WHERE name = 'Alice';

联合索引key_len计算:

ALTER TABLE user_profile DROP INDEX idx_name, DROP INDEX idx_age;
ALTER TABLE user_profile ADD INDEX idx_name_age(name, age);
  1. 部分索引生效
EXPLAIN SELECT * FROM user_profile WHERE name = 'Alice';
  1. 联合索引完全使用
EXPLAIN SELECT * FROM user_profile WHERE name = 'Alice' AND age = 25;

3.7、ref字段

ref表示索引的哪一列被使用,以及哪些列或常量用于查找索引列的值。

  • 常量引用
EXPLAIN SELECT * FROM employees WHERE id = '1';
  • 关联表引用
EXPLAIN SELECT * FROM employees e LEFT JOIN departments d ON e.id = d.id WHERE e.name = 'Alice';

驱动表概念: 在多表关联查询中,第一个被处理的表是驱动表,使用驱动表去关联其他表。驱动表的选择应遵循在不影响最终结果的前提下,优先选择结果集最小的表作为驱动表。

3.8、rows字段

rows表示MySQL根据统计信息和索引选用情况,估算的所需记录行数;越少越好。

  1. 使用LIKE查询,产生全表扫描
EXPLAIN SELECT * FROM employees WHERE name LIKE '%A%';
  1. 使用等值查询,直接找到记录
EXPLAIN SELECT * FROM employees WHERE name = 'Alice';

3.9、filtered字段

filtered表示返回结果的行占需要读到的行(rows列的值)的百分比。

3.10、extra字段

Extra显示MySQL查询过程中的额外信息。

  • Using filesort:需要文件排序,性能较差
CREATE TABLE test_data (id INT, value VARCHAR(10));
INSERT INTO test_data VALUES(1, 'A'), (2, 'C'), (3, 'B');
EXPLAIN SELECT * FROM test_data ORDER BY value;

优化方案:在ORDER BY的列上添加索引。

  • Using temporary:需要创建临时表,性能消耗大
EXPLAIN SELECT COUNT(*), name FROM employees GROUP BY name;

优化方案:确保WHERE和GROUP BY使用相同的索引字段。

  • Using where:使用WHERE条件过滤数据
EXPLAIN SELECT * FROM employees WHERE age > 30;

注意:即使使用了WHERE条件,仍需配合type字段综合判断性能。

  • Using index:使用覆盖索引,无需回表
CREATE INDEX idx_name ON employees(name);
EXPLAIN SELECT id, name FROM employees WHERE name = 'Alice';
  • Using join buffer (Block Nested Loop):使用连接缓冲区执行嵌套循环连接
EXPLAIN SELECT * 
FROM employees e1 
LEFT JOIN 
    (SELECT * FROM employees WHERE department_id = 1) e2 
ON e1.id = e2.id;

优化方案:在关联字段上添加索引。

  • Using index condition:索引条件下推优化
CREATE TABLE products (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50),
    category VARCHAR(50),
    price DECIMAL(10, 2),
    stock INT
);

INSERT INTO products (name, category, price, stock) VALUES
('Laptop', 'Electronics', 999.99, 10),
('Phone', 'Electronics', 699.99, 15),
('Desk', 'Furniture', 199.99, 5),
('Chair', 'Furniture', 89.99, 20);

CREATE INDEX idx_category_price ON products(category, price);
EXPLAIN SELECT * FROM products WHERE category = 'Electronics' AND price > 500 AND stock > 10;

这个例子展示了Index Condition Pushdown (ICP)优化,MySQL在存储引擎层面应用部分WHERE条件,减少需要从表中读取的行数。

总结:通过分析EXPLAIN执行计划,我们可以了解SQL查询的执行方式,识别性能瓶颈,并针对性地进行优化。重点关注表的访问类型(type)、实际使用的索引(key)以及额外信息(extra),这些是判断查询性能的关键指标。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。