Skip to content

MySQL EXPLAIN 实战教程

基于 Sakila 示例数据库的 EXPLAIN 学习指南

目录

  1. 环境准备
  2. EXPLAIN 基础
  3. 输出列详解
  4. type 执行计划类型
  5. 索引分析
  6. 实战案例
  7. 优化建议
  8. 面试高频问题

环境准备

1. 导入数据库

bash
# 创建数据库并导入 schema
mysql -u root -p < sakila-schema.sql

# 导入数据
mysql -u root -p < sakila-data.sql

2. 验证数据

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

特性EXPLAINEXPLAIN 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) = 2

6. 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_id

7. 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 away

type 执行计划类型详解

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_name

4. fulltext - 全文索引

sql
-- 需要创建全文索引
ALTER TABLE film ADD FULLTEXT INDEX ft_title_description(title, description);

EXPLAIN SELECT * FROM film WHERE MATCH(title, description) AGAINST('action');
-- type: fulltext

5. ref_or_null - 包含 NULL 的引用

sql
-- 查找索引值,同时查找 NULL 值

EXPLAIN SELECT * FROM customer WHERE address_id = 5 OR address_id IS NULL;
-- 可能使用 ref_or_null

6. 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_rating

7. 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: 哪些情况会导致索引失效?

  1. 前导通配符 LIKE '%xxx'
  2. 索引列使用函数
  3. 隐式类型转换
  4. OR 连接未建索引的列
  5. !=<>(数据量大时)
  6. IS NULL(可能)

Q4: 如何优化深分页?

  1. 使用延迟关联
  2. 记录上次位置
  3. 使用 ES 等搜索引擎
  4. 限制分页深度

Q5: 覆盖索引是什么?

:查询的列都包含在索引中,不需要回表查询数据行。

sql
-- 假设有索引 idx_title (title)
EXPLAIN SELECT title FROM film WHERE title = 'XXX';
-- Extra: Using index (覆盖索引)

Q6: 什么是索引下推(ICP)?

:MySQL 5.6+ 特性,在存储引擎层进行索引条件过滤,减少回表次数。

Q7: 如何分析 SQL 性能瓶颈?

  1. 使用 EXPLAIN 分析执行计划
  2. 使用 EXPLAIN ANALYZE 查看实际执行时间
  3. 使用 SHOW PROFILE 分析各阶段耗时
  4. 使用 slow query log 定位慢查询

Q8: JOIN 优化策略?

  1. 小表驱动大表
  2. 确保连接列有索引
  3. 只查询需要的列
  4. 合理使用 INNER JOIN vs LEFT JOIN
  5. 考虑使用 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_rating

Q10: 如何查看 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;

学习建议

  1. 先理解基础概念
  2. 在 Sakila 数据库上实践每个案例
  3. 对比修改前后的 EXPLAIN 输出
  4. 记录常见的优化模式
  5. 定期复习和练习