Appearance
MySQL EXPLAIN 实战教程
基于 Sakila 示例数据库的 EXPLAIN 学习指南
目录
环境准备
1. 导入数据库
bash
# 创建数据库并导入 schema
mysql -u root -p < sakila-schema.sql
# 导入数据
mysql -u root -p < sakila-data.sql2. 验证数据
sql
USE sakila;
-- 查看所有表
SHOW TABLES;
-- 查看表结构
DESCRIBE film;
DESCRIBE rental;
DESCRIBE payment;
DESCRIBE customer;
DESCRIBE inventory;
-- 查看索引
SHOW INDEX FROM film;
SHOW INDEX FROM rental;EXPLAIN 基础
基本语法
sql
-- 基本用法
EXPLAIN SELECT * FROM film WHERE film_id = 1;
-- 格式化输出(MySQL 8.0+)
EXPLAIN FORMAT=TREE SELECT * FROM film WHERE film_id = 1;
-- JSON 格式
EXPLAIN FORMAT=JSON SELECT * FROM film WHERE film_id = 1;
-- 分析实际执行情况(MySQL 8.0.18+)
EXPLAIN ANALYZE SELECT * FROM film WHERE film_id = 1;EXPLAIN vs EXPLAIN ANALYZE
| 特性 | EXPLAIN | EXPLAIN ANALYZE |
|---|---|---|
| 执行查询 | ❌ 不执行 | ✅ 真实执行 |
| 真实时间 | ❌ 估算 | ✅ 实际测量 |
| 适用场景 | 快速分析 | 精确性能分析 |
sql
-- 普通EXPLAIN - 只显示计划
EXPLAIN SELECT * FROM film;
-- EXPLAIN ANALYZE - 显示实际执行时间
EXPLAIN ANALYZE SELECT * FROM film;输出列详解
完整输出结构
+----+-------------+-------+------------+------+---------------+-----+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-----+---------+------+------+----------+-------+1. id - 查询序列号
简单查询:id 相同
sql
EXPLAIN SELECT * FROM film WHERE film_id = 1;
-- 结果:id = 1子查询:不同 id
sql
EXPLAIN
SELECT * FROM film
WHERE film_id IN (SELECT film_id FROM film_actor WHERE actor_id = 1);
-- 结果:外层 id=1,内层 id=2实际结果两行都是 id=1,因为 MySQL 优化器对 IN 子查询进行了查询重写,优化器认为 JOIN 方式更高效,所以自动进行了优化。
重写后的 SQL:
SELECT film.* FROM film INNER JOIN film_actor ON film.film_id = film_actor.film_id WHERE film_actor.actor_id = 1
id 相同,执行顺序从上到下
sql
EXPLAIN
SELECT f.title, a.first_name
FROM film f
JOIN film_actor fa ON f.film_id = fa.film_id
JOIN actor a ON fa.actor_id = a.actor_id
WHERE a.actor_id = 1;id 不同,数字大的先执行(子查询)
sql
EXPLAIN
SELECT * FROM film
WHERE film_id IN (SELECT film_id FROM film_actor WHERE actor_id = 1);2. select_type - 查询类型
sql
-- 1. SIMPLE:简单查询(无子查询、无 UNION)
EXPLAIN SELECT * FROM film WHERE film_id = 1;
-- 2. PRIMARY:最外层查询
EXPLAIN
SELECT * FROM film
WHERE film_id IN (SELECT film_id FROM film_actor WHERE actor_id = 1);
-- 3. SUBQUERY:子查询(不在 FROM 子句中)
-- 见上面示例,内层查询
-- 4. DERIVED:派生表(FROM 子句中的子查询)
EXPLAIN
SELECT * FROM (
SELECT film_id, title FROM film WHERE rating = 'PG'
) AS pg_films;
-- 5. UNION:UNION 中的第二个或后面的 SELECT
EXPLAIN
SELECT film_id FROM film WHERE rating = 'G'
UNION
SELECT film_id FROM film WHERE rating = 'PG';
-- 6. UNION RESULT:UNION 的结果
-- 见上面示例
-- 7. DEPENDENT SUBQUERY:依赖外部查询的子查询
EXPLAIN
SELECT * FROM film f
WHERE EXISTS (
SELECT 1 FROM film_actor fa
WHERE fa.film_id = f.film_id AND fa.actor_id = 1
);3. type - 访问类型(重要!)
性能从好到差:system > const > eq_ref > ref > range > index > ALL
sql
-- 1. system/const:单行匹配(最优)
EXPLAIN SELECT * FROM film WHERE film_id = 1;
-- 2. eq_ref:唯一索引扫描,每个索引值只匹配一行
EXPLAIN
SELECT * FROM film f
JOIN film_actor fa ON f.film_id = fa.film_id;2 的 type 是 ref,因为主键索引 (actor_id, film_id) 是最左前缀匹配,需要先有 actor_id 才能有效使用。而 film_actor 的另一个索引 idx_fk_film_id 索引是单列 film_id 索引,更适合这个查询
sql
-- eq_ref:唯一索引扫描,每个索引值只匹配一行
EXPLAIN SELECT f.*, c.name
FROM film f
JOIN film_category fc ON f.film_id = fc.film_id
JOIN category c ON fc.category_id = c.category_id
WHERE f.film_id BETWEEN 1 AND 5;这个例子:
- fc(film_category)是驱动表,使用 range 扫描
- f(film)是被驱动表,使用 eq_ref(通过 PRIMARY 键连接)
- c(category)也是被驱动表,使用 eq_ref(通过 PRIMARY 键连接)
sql
-- 3. ref:非唯一索引扫描
EXPLAIN SELECT * FROM customer WHERE last_name = 'SMITH';
-- 4. range:范围扫描
EXPLAIN SELECT * FROM film WHERE film_id BETWEEN 1 AND 100;
-- 5. index:索引全扫描
EXPLAIN SELECT film_id FROM film;
-- 6. ALL:全表扫描(需要优化)
EXPLAIN SELECT * FROM customer WHERE email LIKE '%gmail.com';4. possible_keys 与 key
sql
-- possible_keys:可能使用的索引
-- key:实际使用的索引
-- 索引存在但未使用
EXPLAIN SELECT * FROM film WHERE title LIKE 'AC%';
-- possible_keys: idx_title
-- key: idx_title
-- 索引存在,优化器选择不用
EXPLAIN SELECT * FROM customer WHERE last_name = 'SMITH' AND active = 1;
-- 可能不用索引,因为数据量小5. key_len - 索引使用长度
sql
-- 计算公式:索引字节数
-- VARCHAR(N) * 字符集 + NULL标志(1或2) + 长度信息(1或2)
-- 演示
CREATE TABLE test_keylen (
id INT,
name VARCHAR(10),
age TINYINT,
KEY idx_name (name),
KEY idx_age (age)
);
EXPLAIN SELECT * FROM test_keylen WHERE name = 'test';
-- key_len = 10 * 4 (utf8mb4) + 2 (长度) + 1 (NULL) = 43
EXPLAIN SELECT * FROM test_keylen WHERE age = 25;
-- key_len = 1 (TINYINT) + 1 (NULL) = 26. ref - 索引比较的列
sql
-- 常量比较
EXPLAIN SELECT * FROM film WHERE film_id = 1;
-- ref: const
-- 列比较
EXPLAIN
SELECT * FROM film f
JOIN film_actor fa ON f.film_id = fa.film_id;
-- ref: sakila.f.film_id7. rows - 估算扫描行数
sql
-- 行数越少越好
EXPLAIN SELECT * FROM film WHERE film_id = 1;
-- rows: 1
EXPLAIN SELECT * FROM film WHERE rental_rate = 4.99;
-- rows: 较大8. filtered - 过滤百分比
sql
-- MySQL 5.7+ 显示,值越大越好
EXPLAIN SELECT * FROM film WHERE film_id = 1;
-- filtered: 100.00 (100% 的行满足条件)
EXPLAIN SELECT * FROM film WHERE rental_rate = 4.99 AND rating = 'PG';
-- filtered: 可能较低9. Extra - 额外信息(重要!)
sql
-- 1. Using index:覆盖索引(最优)
EXPLAIN SELECT film_id, title FROM film WHERE title = 'ACADEMY DINOSAUR';
-- Extra: Using index (如果 title 和 film_id 都在索引中)
-- 2. Using where:使用 WHERE 过滤
EXPLAIN SELECT * FROM film WHERE rental_rate > 4.00;
-- Extra: Using where
-- 3. Using index condition:索引下推(MySQL 5.6+)
EXPLAIN SELECT * FROM film WHERE title LIKE 'AC%' AND rental_rate = 4.99;
-- Extra: Using index condition
-- 4. Using filesort:文件排序(需要优化)
EXPLAIN SELECT * FROM customer ORDER BY email;
-- Extra: Using filesort (email 无索引)
-- 5. Using temporary:使用临时表(需要优化)
EXPLAIN
SELECT rating, COUNT(*) FROM film GROUP BY rating ORDER BY COUNT(*);
-- Extra: Using temporary; Using filesort
-- 6. Using join buffer:使用连接缓冲
EXPLAIN
SELECT * FROM customer c
JOIN rental r ON c.customer_id = r.customer_id
WHERE c.active = 1;
-- 可能出现 Using join buffer
-- 7. Impossible WHERE:不可能条件
EXPLAIN SELECT * FROM film WHERE film_id = 1 AND film_id = 2;
-- Extra: Impossible WHERE
-- 8. Select tables optimized away:优化器直接返回结果
EXPLAIN SELECT MIN(film_id) FROM film;
-- Extra: Select tables optimized awaytype 执行计划类型详解
1. system / const - 最好
sql
-- system:表只有一行(MyISAM 引擎)
-- const:通过主键或唯一索引精确匹配
EXPLAIN SELECT * FROM film WHERE film_id = 1;
-- type: const
-- rows: 1
-- Extra:2. eq_ref - 唯一索引引用
sql
-- 连接时使用主键或唯一索引,每行只匹配一行
EXPLAIN
SELECT f.title, fa.actor_id
FROM film f
INNER JOIN film_actor fa ON f.film_id = fa.film_id;
-- film_actor 的 type 应该是 eq_ref(如果 film_id 是主键或唯一)
-- 或者 ref(如果只是普通索引)3. ref - 非唯一索引引用
sql
-- 使用非唯一索引或唯一索引的前缀
EXPLAIN SELECT * FROM customer WHERE last_name = 'SMITH';
-- type: ref
-- possible_keys: idx_last_name
-- key: idx_last_name4. fulltext - 全文索引
sql
-- 需要创建全文索引
ALTER TABLE film ADD FULLTEXT INDEX ft_title_description(title, description);
EXPLAIN SELECT * FROM film WHERE MATCH(title, description) AGAINST('action');
-- type: fulltext5. ref_or_null - 包含 NULL 的引用
sql
-- 查找索引值,同时查找 NULL 值
EXPLAIN SELECT * FROM customer WHERE address_id = 5 OR address_id IS NULL;
-- 可能使用 ref_or_null6. index_merge - 索引合并
sql
-- 同时使用多个索引
CREATE INDEX idx_rental_rate ON film(rental_rate);
CREATE INDEX idx_rating ON film(rating);
EXPLAIN SELECT * FROM film WHERE rental_rate = 4.99 OR rating = 'PG';
-- type: index_merge
-- key: idx_rental_rate,idx_rating7. range - 范围扫描
sql
-- 索引范围扫描:BETWEEN, >, <, IN 等
EXPLAIN SELECT * FROM film WHERE film_id BETWEEN 1 AND 100;
-- type: range
EXPLAIN SELECT * FROM film WHERE rental_rate IN (2.99, 4.99);
-- type: range
EXPLAIN SELECT * FROM rental
WHERE rental_date >= '2005-05-27' AND rental_date <= '2005-05-28';
-- type: range (如果 rental_date 有索引)8. index - 索引全扫描
sql
-- 扫描整个索引树(通常比 ALL 快)
EXPLAIN SELECT film_id FROM film;
-- type: index
-- Extra: Using index (覆盖索引)
EXPLAIN SELECT COUNT(*) FROM film;
-- type: index (如果 MyISAM,可能不走索引)9. ALL - 全表扫描(最差)
sql
-- 需要优化的情况
EXPLAIN SELECT * FROM customer WHERE email LIKE '%gmail.com';
-- type: ALL (前导通配符无法使用索引)
EXPLAIN SELECT * FROM film WHERE LOWER(title) = 'academy dinosaur';
-- type: ALL (函数破坏索引)索引分析
覆盖索引
sql
-- 查询的列都在索引中,不需要回表
-- 不使用覆盖索引
EXPLAIN SELECT * FROM film WHERE title = 'ACADEMY DINOSAUR';
-- type: ref (如果 title 有索引)
-- 需要回表获取其他列
-- 使用覆盖索引
EXPLAIN SELECT film_id, title FROM film WHERE title = 'ACADEMY DINOSAUR';
-- type: ref
-- Extra: Using index (覆盖索引,最优)索引下推(ICP)
sql
-- MySQL 5.6+ 特性,减少回表次数
-- 创建联合索引
CREATE INDEX idx_title_rate ON film(title, rental_rate);
-- 使用索引下推
EXPLAIN SELECT * FROM film
WHERE title LIKE 'AC%' AND rental_rate = 4.99;
-- Extra: Using index condition
-- title 用索引过滤,rental_rate 在索引层过滤,减少回表索引失效场景
sql
-- 1. 前导通配符
EXPLAIN SELECT * FROM customer WHERE email LIKE '%@gmail.com';
-- type: ALL,索引失效
-- 正确用法
EXPLAIN SELECT * FROM customer WHERE email LIKE 'john%';
-- type: range,索引有效
-- 2. 索引列上使用函数
EXPLAIN SELECT * FROM customer WHERE UPPER(email) = 'JOHN@EXAMPLE.COM';
-- type: ALL,索引失效
-- 正确做法
EXPLAIN SELECT * FROM customer WHERE email = 'john@example.com';
-- type: ref,索引有效
-- 3. 隐式类型转换
EXPLAIN SELECT * FROM customer WHERE phone = 123456789;
-- phone 是 VARCHAR,传入数字,索引失效
-- 4. OR 条件
EXPLAIN SELECT * FROM film WHERE film_id = 1 OR rental_rate = 4.99;
-- 如果 film_id 和 rental_rate 都有索引,可能使用 index_merge
-- 如果只有一个有索引,可能不使用索引
-- 5. != 或 <>
EXPLAIN SELECT * FROM film WHERE rental_rate != 4.99;
-- 可能不走索引(数据量大时可能走)
-- 6. IS NULL(可能使用索引)
EXPLAIN SELECT * FROM customer WHERE address_id IS NULL;
-- 如果 address_id 允许 NULL 且有索引,可能使用实战案例
案例 1:优化全表扫描
问题:查询所有租赁记录的客户信息
sql
-- 优化前:全表扫描
EXPLAIN
SELECT c.first_name, c.last_name, r.rental_date, r.return_date
FROM customer c
JOIN rental r ON c.customer_id = r.customer_id
WHERE c.email LIKE '%@example.com';分析:
- type: ALL(全表扫描)
- Extra: Using where; Using join buffer
优化:
sql
-- 1. 添加索引
CREATE INDEX idx_customer_email ON customer(email);
-- 2. 修改查询(去掉前导通配符)
EXPLAIN
SELECT c.first_name, c.last_name, r.rental_date, r.return_date
FROM customer c
JOIN rental r ON c.customer_id = r.customer_id
WHERE c.email LIKE 'john%@example.com';案例 2:优化 filesort
问题:按客户邮箱排序查询
sql
EXPLAIN
SELECT email FROM customer ORDER BY email;
-- Extra: Using filesort优化:添加索引
sql
CREATE INDEX idx_customer_email ON customer(email);
EXPLAIN
SELECT email FROM customer ORDER BY email;
-- Extra: Using index案例 3:优化 GROUP BY
问题:统计每个电影分级的数量
sql
-- 优化前
EXPLAIN
SELECT rating, COUNT(*) as cnt
FROM film
WHERE rental_rate > 2
GROUP BY rating
ORDER BY cnt DESC;
-- Extra: Using where; Using temporary; Using filesort优化:
sql
-- 添加覆盖索引
CREATE INDEX idx_rating_rate ON film(rating, rental_rate);
EXPLAIN
SELECT rating, COUNT(*) as cnt
FROM film
WHERE rental_rate > 2
GROUP BY rating;
-- type: index 或 ref
-- 减少临时表和文件排序案例 4:优化子查询
问题:查找有演员参演的电影
sql
-- 优化前:子查询
EXPLAIN
SELECT * FROM film
WHERE film_id IN (
SELECT film_id FROM film_actor WHERE actor_id = 1
);优化为 JOIN:
sql
EXPLAIN
SELECT DISTINCT f.*
FROM film f
INNER JOIN film_actor fa ON f.film_id = fa.film_id
WHERE fa.actor_id = 1;进一步优化(如果只需要 film_id):
sql
EXPLAIN
SELECT film_id FROM film_actor WHERE actor_id = 1;
-- type: ref (如果有索引)
-- Extra: Using index案例 5:优化 OR 查询
sql
-- 问题查询
EXPLAIN
SELECT * FROM payment
WHERE customer_id = 1 OR staff_id = 1;优化 1:使用 UNION
sql
EXPLAIN
SELECT * FROM payment WHERE customer_id = 1
UNION
SELECT * FROM payment WHERE staff_id = 1;优化 2:复合索引(如果经常一起查询)
sql
CREATE INDEX idx_customer_staff ON payment(customer_id, staff_id);
EXPLAIN
SELECT * FROM payment
WHERE customer_id = 1 OR staff_id = 1;
-- 可能使用 index_merge案例 6:分页优化
sql
-- 深分页问题
EXPLAIN
SELECT * FROM rental
ORDER BY rental_date DESC
LIMIT 10000, 20;
-- rows: 很大,需要扫描前 10020 行
-- 优化方案 1:使用延迟关联
EXPLAIN
SELECT r.*
FROM rental r
INNER JOIN (
SELECT rental_id FROM rental
ORDER BY rental_date DESC
LIMIT 10000, 20
) AS tmp ON r.rental_id = tmp.rental_id;
-- 优化方案 2:记录上次的最大 ID
SELECT * FROM rental
WHERE rental_id > last_max_id
ORDER BY rental_id
LIMIT 20;案例 7:JOIN 顺序优化
sql
-- 多表 JOIN
EXPLAIN
SELECT c.first_name, c.last_name, f.title
FROM customer c
JOIN rental r ON c.customer_id = r.customer_id
JOIN inventory i ON r.inventory_id = i.inventory_id
JOIN film f ON i.film_id = f.film_id
WHERE c.email LIKE 'a%';
-- 分析:
-- 小表驱动大表
-- 确保连接列有索引
-- STRAIGHT_JOIN 强制连接顺序案例 8:IN vs EXISTS
sql
-- IN:适合外表小,内表大
EXPLAIN
SELECT * FROM customer
WHERE customer_id IN (
SELECT customer_id FROM rental WHERE rental_date > '2005-06-01'
);
-- EXISTS:适合外表大,内表小
EXPLAIN
SELECT * FROM customer c
WHERE EXISTS (
SELECT 1 FROM rental r
WHERE r.customer_id = c.customer_id
AND r.rental_date > '2005-06-01'
);优化建议
1. 索引设计原则
sql
-- 1. 选择性高的列建索引
SELECT COUNT(DISTINCT last_name) / COUNT(*) FROM customer;
-- 接近 1 表示选择性好
-- 2. 联合索引最左前缀
CREATE INDEX idx_a_b_c ON table(a, b, c);
-- 可用:a, a,b, a,b,c
-- 不可用:b, c, b,c
-- 3. 覆盖索引减少回表
CREATE INDEX idx_covering ON film(title, rental_rate, rating);
-- 4. 不要过度索引
-- 每个 INSERT/UPDATE/DELETE 都需要更新索引2. 查询重写技巧
sql
-- 1. 避免 SELECT *
SELECT film_id, title FROM film; -- 好
SELECT * FROM film; -- 差
-- 2. 避免在 WHERE 子句中使用函数
-- 差
SELECT * FROM customer WHERE YEAR(create_date) = 2005;
-- 好
SELECT * FROM customer
WHERE create_date >= '2005-01-01' AND create_date < '2006-01-01';
-- 3. 使用 UNION ALL 代替 UNION(如果无重复)
-- 差
SELECT film_id FROM film WHERE rating = 'G'
UNION
SELECT film_id FROM film WHERE rating = 'PG';
-- 好
SELECT film_id FROM film WHERE rating = 'G'
UNION ALL
SELECT film_id FROM film WHERE rating = 'PG';
-- 4. 批量插入
INSERT INTO customer (first_name, last_name, email) VALUES
('John', 'Doe', 'john@example.com'),
('Jane', 'Smith', 'jane@example.com');3. 表结构优化
sql
-- 1. 选择合适的数据类型
-- 原则:越小越好,简单越好
-- 2. 垂直分表:不常用字段分离
-- 3. 水平分表:数据量大时按时间/ID 分表
-- 4. 适当使用 ENUM
-- 5. 使用 NOT NULL(减少空值判断)面试高频问题
Q1: EXPLAIN 各列的含义?
答:见"输出列详解"章节,重点关注:
type:访问类型key:实际使用索引rows:扫描行数Extra:额外信息
Q2: type 列的值及性能排序?
答:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > range > index > ALL
Q3: 哪些情况会导致索引失效?
答:
- 前导通配符
LIKE '%xxx' - 索引列使用函数
- 隐式类型转换
OR连接未建索引的列!=或<>(数据量大时)IS NULL(可能)
Q4: 如何优化深分页?
答:
- 使用延迟关联
- 记录上次位置
- 使用 ES 等搜索引擎
- 限制分页深度
Q5: 覆盖索引是什么?
答:查询的列都包含在索引中,不需要回表查询数据行。
sql
-- 假设有索引 idx_title (title)
EXPLAIN SELECT title FROM film WHERE title = 'XXX';
-- Extra: Using index (覆盖索引)Q6: 什么是索引下推(ICP)?
答:MySQL 5.6+ 特性,在存储引擎层进行索引条件过滤,减少回表次数。
Q7: 如何分析 SQL 性能瓶颈?
答:
- 使用
EXPLAIN分析执行计划 - 使用
EXPLAIN ANALYZE查看实际执行时间 - 使用
SHOW PROFILE分析各阶段耗时 - 使用
slow query log定位慢查询
Q8: JOIN 优化策略?
答:
- 小表驱动大表
- 确保连接列有索引
- 只查询需要的列
- 合理使用 INNER JOIN vs LEFT JOIN
- 考虑使用 STRAIGHT_JOIN 强制顺序
Q9: 什么时候使用索引合并?
答:当查询条件可以使用多个独立索引时。
sql
-- 假设 rental_rate 和 rating 都有索引
SELECT * FROM film WHERE rental_rate = 4.99 OR rating = 'PG';
-- type: index_merge
-- key: idx_rental_rate,idx_ratingQ10: 如何查看 SQL 实际执行成本?
答:MySQL 8.0+
sql
EXPLAIN FORMAT=JSON SELECT ...;
-- 返回 cost 信息
EXPLAIN ANALYZE SELECT ...;
-- 显示实际执行时间和行数练习题
练习 1:分析并优化
sql
EXPLAIN
SELECT * FROM rental
WHERE DATE(rental_date) = '2005-05-27'
ORDER BY rental_date;练习 2:索引设计
为以下查询设计最优索引:
sql
SELECT film_id, title, rental_rate
FROM film
WHERE rating = 'PG' AND rental_rate > 3
ORDER BY title;练习 3:JOIN 优化
分析并优化:
sql
SELECT c.first_name, c.last_name, COUNT(r.rental_id)
FROM customer c
LEFT JOIN rental r ON c.customer_id = r.customer_id
WHERE c.active = 1
GROUP BY c.customer_id
HAVING COUNT(r.rental_id) > 10;练习 4:子查询转 JOIN
sql
SELECT * FROM film
WHERE film_id IN (
SELECT film_id FROM film_actor
WHERE actor_id IN (
SELECT actor_id FROM actor WHERE last_name = 'CRUZ'
)
);参考命令速查
sql
-- 查看表索引
SHOW INDEX FROM table_name;
-- 查看索引基数
SHOW INDEX FROM table_name WHERE Column_name = 'xxx';
-- 查看表统计信息
SHOW TABLE STATUS LIKE 'table_name';
-- 分析表
ANALYZE TABLE table_name;
-- 检查表
CHECK TABLE table_name;
-- 优化表
OPTIMIZE TABLE table_name;
-- 查看执行计划
EXPLAIN SELECT ...;
-- 真实执行分析(MySQL 8.0.18+)
EXPLAIN ANALYZE SELECT ...;
-- JSON 格式执行计划
EXPLAIN FORMAT=JSON SELECT ...;
-- 树形执行计划(MySQL 8.0+)
EXPLAIN FORMAT=TREE SELECT ...;
-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 2;
-- 查看 Profile
SET profiling = 1;
SELECT ...;
SHOW PROFILES;
SHOW PROFILE FOR QUERY 1;学习建议:
- 先理解基础概念
- 在 Sakila 数据库上实践每个案例
- 对比修改前后的 EXPLAIN 输出
- 记录常见的优化模式
- 定期复习和练习