EXPLAIN:分析SQL语句执行计划。
东巴文理解:
EXPLAIN = 体检报告
体检报告显示身体状况
EXPLAIN显示SQL执行情况
找出性能问题所在
语法格式:
EXPLAIN SELECT语句;
EXPLAIN ANALYZE SELECT语句; -- MySQL 8.0+
示例:
-- 创建测试表
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
email VARCHAR(100),
dept_id INT,
INDEX idx_name (name),
INDEX idx_dept (dept_id)
);
-- 使用EXPLAIN
EXPLAIN SELECT * FROM users WHERE name = '张三';
重要字段:
| 字段 | 说明 |
|---|---|
| id | 查询标识符 |
| select_type | 查询类型 |
| table | 表名 |
| type | 访问类型 |
| possible_keys | 可能使用的索引 |
| key | 实际使用的索引 |
| key_len | 使用的索引长度 |
| ref | 索引比较的列 |
| rows | 预估扫描行数 |
| Extra | 额外信息 |
type字段值(从好到差):
| 类型 | 说明 |
|---|---|
| system | 单行表 |
| const | 单行匹配 |
| eq_ref | 唯一索引扫描 |
| ref | 非唯一索引扫描 |
| range | 范围扫描 |
| index | 索引扫描 |
| ALL | 全表扫描 |
示例:
-- system:系统表
EXPLAIN SELECT * FROM mysql.proxies_priv LIMIT 1;
-- const:主键查询
EXPLAIN SELECT * FROM users WHERE id = 1;
-- eq_ref:JOIN时使用主键
EXPLAIN SELECT * FROM users u JOIN orders o ON u.id = o.user_id;
-- ref:非唯一索引
EXPLAIN SELECT * FROM users WHERE name = '张三';
-- range:范围查询
EXPLAIN SELECT * FROM users WHERE id BETWEEN 1 AND 100;
-- index:索引扫描
EXPLAIN SELECT id FROM users;
-- ALL:全表扫描
EXPLAIN SELECT * FROM users WHERE email = 'test@example.com';
常见值:
| 值 | 说明 |
|---|---|
| Using index | 使用覆盖索引 |
| Using where | 使用WHERE过滤 |
| Using temporary | 使用临时表 |
| Using filesort | 使用文件排序 |
| Using join buffer | 使用连接缓冲 |
示例:
-- Using index:覆盖索引
EXPLAIN SELECT id, name FROM users WHERE name = '张三';
-- Using temporary:临时表
EXPLAIN SELECT dept_id, COUNT(*) FROM users GROUP BY dept_id;
-- Using filesort:文件排序
EXPLAIN SELECT * FROM users ORDER BY email;
查看状态:
-- 查看慢查询日志状态
SHOW VARIABLES LIKE 'slow_query_log%';
SHOW VARIABLES LIKE 'long_query_time';
开启慢查询日志:
-- 临时开启
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 2;
SET GLOBAL slow_query_log_file = '/var/log/mysql/mysql-slow.log';
-- 永久开启(配置文件)
[mysqld]
slow_query_log = ON
long_query_time = 2
slow_query_log_file = /var/log/mysql/mysql-slow.log
使用mysqldumpslow:
# 查看慢查询统计
mysqldumpslow -s t /var/log/mysql/mysql-slow.log
# 查询最多的10条
mysqldumpslow -s c -t 10 /var/log/mysql/mysql-slow.log
# 平均查询时间最长的10条
mysqldumpslow -s at -t 10 /var/log/mysql/mysql-slow.log
使用pt-query-digest:
# 安装Percona Toolkit
apt-get install percona-toolkit
# 分析慢查询日志
pt-query-digest /var/log/mysql/mysql-slow.log > slow_report.txt
示例:
-- 开启profiling
SET profiling = ON;
-- 执行查询
SELECT * FROM users WHERE name = '张三';
-- 查看profile
SHOW PROFILES;
-- 查看详细profile
SHOW PROFILE FOR QUERY 1;
-- 查看特定资源
SHOW PROFILE CPU, BLOCK IO FOR QUERY 1;
状态值:
| 状态 | 说明 |
|---|---|
| starting | 开始 |
| checking permissions | 检查权限 |
| Opening tables | 打开表 |
| init | 初始化 |
| System lock | 系统锁 |
| optimizing | 优化 |
| statistics | 统计 |
| preparing | 准备 |
| executing | 执行 |
| Sending data | 发送数据 |
| end | 结束 |
| query end | 查询结束 |
| closing tables | 关闭表 |
| freeing items | 释放资源 |
| cleaning up | 清理 |
查看状态:
-- 查看Performance Schema状态
SHOW VARIABLES LIKE 'performance_schema';
启用Performance Schema:
[mysqld]
performance_schema = ON
示例:
-- 查看等待事件
SELECT * FROM performance_schema.events_waits_summary_global_by_event_name
WHERE COUNT_STAR > 0
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;
-- 查看文件I/O
SELECT * FROM performance_schema.file_summary_by_instance
ORDER BY COUNT_READ DESC
LIMIT 10;
-- 查看表I/O
SELECT * FROM performance_schema.table_io_waits_summary_by_table
WHERE COUNT_READ > 0
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;
适合创建索引的列:
不适合创建索引的列:
示例:
-- 创建复合索引
CREATE INDEX idx_name_dept_salary ON users(name, dept_id, salary);
-- 使用索引(符合最左前缀)
SELECT * FROM users WHERE name = '张三';
SELECT * FROM users WHERE name = '张三' AND dept_id = 1;
SELECT * FROM users WHERE name = '张三' AND dept_id = 1 AND salary > 10000;
-- 不使用索引(不符合最左前缀)
SELECT * FROM users WHERE dept_id = 1;
SELECT * FROM users WHERE salary > 10000;
1. 使用函数:
-- 索引失效
SELECT * FROM users WHERE YEAR(created_at) = 2024;
-- 索引有效
SELECT * FROM users WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';
2. 隐式类型转换:
-- 索引失效(字符串列使用数字)
SELECT * FROM users WHERE phone_number = 13800138000;
-- 索引有效(使用字符串)
SELECT * FROM users WHERE phone_number = '13800138000';
3. 使用OR:
-- 索引失效
SELECT * FROM users WHERE name = '张三' OR email = 'test@example.com';
-- 索引有效(使用UNION)
SELECT * FROM users WHERE name = '张三'
UNION
SELECT * FROM users WHERE email = 'test@example.com';
4. 使用NOT:
-- 索引失效
SELECT * FROM users WHERE name NOT LIKE '张%';
-- 索引有效
SELECT * FROM users WHERE name LIKE '李%';
5. 使用!=或<>:
-- 索引失效
SELECT * FROM users WHERE dept_id != 1;
-- 索引有效
SELECT * FROM users WHERE dept_id > 1;
6. 使用IS NULL或IS NOT NULL:
-- 索引失效
SELECT * FROM users WHERE name IS NULL;
-- 建议:设置默认值
ALTER TABLE users MODIFY name VARCHAR(50) DEFAULT '';
7. LIKE以通配符开头:
-- 索引失效
SELECT * FROM users WHERE name LIKE '%张%';
-- 索引有效
SELECT * FROM users WHERE name LIKE '张%';
覆盖索引:查询所需的所有字段都在索引中,无需回表。
示例:
-- 创建索引
CREATE INDEX idx_name_email ON users(name, email);
-- 使用覆盖索引
EXPLAIN SELECT name, email FROM users WHERE name = '张三';
-- Extra: Using index
索引下推(ICP):在索引遍历过程中进行过滤,减少回表次数。
示例:
-- 创建索引
CREATE INDEX idx_name_dept ON users(name, dept_id);
-- 不使用索引下推(MySQL 5.6之前)
-- 1. 通过索引找到所有name='张三'的记录
-- 2. 回表查询完整记录
-- 3. 过滤dept_id=1的记录
-- 使用索引下推(MySQL 5.6+)
-- 1. 通过索引找到name='张三'且dept_id=1的记录
-- 2. 回表查询完整记录
EXPLAIN SELECT * FROM users WHERE name = '张三' AND dept_id = 1;
-- Extra: Using index condition
不推荐:
SELECT * FROM users WHERE id = 1;
推荐:
SELECT id, name, email FROM users WHERE id = 1;
示例:
-- 只需要一条记录
SELECT * FROM users WHERE name = '张三' LIMIT 1;
-- 分页查询
SELECT * FROM users LIMIT 0, 10;
不推荐:
SELECT * FROM users
WHERE dept_id IN (SELECT id FROM departments WHERE dept_name = '技术部');
推荐:
SELECT u.*
FROM users u
JOIN departments d ON u.dept_id = d.id
WHERE d.dept_name = '技术部';
不推荐:
SELECT * FROM users WHERE name = '张三' OR email = 'test@example.com';
推荐:
SELECT * FROM users WHERE name = '张三'
UNION
SELECT * FROM users WHERE email = 'test@example.com';
问题:
-- 偏移量大时性能差
SELECT * FROM users LIMIT 1000000, 10;
优化方案:
-- 使用覆盖索引
SELECT u.* FROM users u
JOIN (SELECT id FROM users LIMIT 1000000, 10) t
ON u.id = t.id;
优化方案:
-- 第一页
SELECT * FROM users WHERE id > 0 ORDER BY id LIMIT 10;
-- 下一页(假设上一页最后一条id=10)
SELECT * FROM users WHERE id > 10 ORDER BY id LIMIT 10;
示例:
-- 创建索引
CREATE INDEX idx_salary ON users(salary);
-- 使用索引排序
EXPLAIN SELECT * FROM users ORDER BY salary;
-- Extra: Using index
不推荐:
-- 使用文件排序
SELECT * FROM users ORDER BY RAND();
推荐:
-- 使用索引排序
SELECT * FROM users ORDER BY id;
示例:
-- 创建索引
CREATE INDEX idx_dept ON users(dept_id);
-- 使用索引分组
EXPLAIN SELECT dept_id, COUNT(*) FROM users GROUP BY dept_id;
-- Extra: Using index
不推荐:
-- 使用临时表
SELECT dept_id, COUNT(*) FROM users GROUP BY dept_id ORDER BY COUNT(*) DESC;
推荐:
-- 使用索引
SELECT dept_id, COUNT(*) as cnt FROM users GROUP BY dept_id ORDER BY dept_id;
整数类型:
-- 不推荐
CREATE TABLE users (
id INT(11),
age INT(11)
);
-- 推荐
CREATE TABLE users (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
age TINYINT UNSIGNED
);
字符串类型:
-- 不推荐
CREATE TABLE users (
name VARCHAR(255),
phone VARCHAR(255)
);
-- 推荐
CREATE TABLE users (
name VARCHAR(50),
phone CHAR(11)
);
不推荐:
CREATE TABLE users (
id INT,
name VARCHAR(50) NULL
);
推荐:
CREATE TABLE users (
id INT NOT NULL,
name VARCHAR(50) NOT NULL DEFAULT ''
);
RANGE分区:
CREATE TABLE orders (
id INT,
order_date DATE,
amount DECIMAL(10, 2)
)
PARTITION BY RANGE (YEAR(order_date)) (
PARTITION p2020 VALUES LESS THAN (2021),
PARTITION p2021 VALUES LESS THAN (2022),
PARTITION p2022 VALUES LESS THAN (2023),
PARTITION pmax VALUES LESS THAN MAXVALUE
);
LIST分区:
CREATE TABLE users (
id INT,
region_id INT
)
PARTITION BY LIST (region_id) (
PARTITION p_north VALUES IN (1, 2, 3),
PARTITION p_south VALUES IN (4, 5, 6),
PARTITION p_east VALUES IN (7, 8, 9),
PARTITION p_west VALUES IN (10, 11, 12)
);
HASH分区:
CREATE TABLE users (
id INT,
name VARCHAR(50)
)
PARTITION BY HASH (id)
PARTITIONS 4;
添加分区:
ALTER TABLE orders ADD PARTITION (
PARTITION p2023 VALUES LESS THAN (2024)
);
删除分区:
ALTER TABLE orders DROP PARTITION p2020;
查看分区:
SELECT * FROM information_schema.PARTITIONS
WHERE TABLE_NAME = 'orders';
拆分前:
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50),
email VARCHAR(100),
address TEXT,
profile TEXT,
preferences TEXT
);
拆分后:
-- 基础信息表
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50),
email VARCHAR(100)
);
-- 详细信息表
CREATE TABLE user_profiles (
user_id INT PRIMARY KEY,
address TEXT,
profile TEXT,
preferences TEXT
);
拆分前:
CREATE TABLE orders (
id INT PRIMARY KEY,
user_id INT,
order_date DATE,
amount DECIMAL(10, 2)
);
拆分后:
-- 订单表1
CREATE TABLE orders_2023 (
id INT PRIMARY KEY,
user_id INT,
order_date DATE,
amount DECIMAL(10, 2)
);
-- 订单表2
CREATE TABLE orders_2024 (
id INT PRIMARY KEY,
user_id INT,
order_date DATE,
amount DECIMAL(10, 2)
);
查看配置:
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
设置建议:
[mysqld]
# 设置为物理内存的70-80%
innodb_buffer_pool_size = 4G
MySQL 8.0已移除查询缓存。
MySQL 5.7及以下:
[mysqld]
query_cache_type = 1
query_cache_size = 64M
查看配置:
SHOW VARIABLES LIKE 'max_connections';
设置建议:
[mysqld]
max_connections = 1000
查看配置:
SHOW VARIABLES LIKE 'wait_timeout';
SHOW VARIABLES LIKE 'interactive_timeout';
设置建议:
[mysqld]
wait_timeout = 28800
interactive_timeout = 28800
查看配置:
SHOW VARIABLES LIKE 'binlog%';
设置建议:
[mysqld]
log-bin = mysql-bin
binlog_format = ROW
binlog_cache_size = 1M
max_binlog_size = 100M
expire_logs_days = 7
设置建议:
[mysqld]
slow_query_log = ON
long_query_time = 2
slow_query_log_file = /var/log/mysql/mysql-slow.log
查看配置:
SHOW VARIABLES LIKE 'tmp_table_size';
SHOW VARIABLES LIKE 'max_heap_table_size';
设置建议:
[mysqld]
tmp_table_size = 64M
max_heap_table_size = 64M
查看配置:
SHOW VARIABLES LIKE 'sort_buffer_size';
设置建议:
[mysqld]
sort_buffer_size = 2M
问题SQL:
SELECT * FROM orders WHERE YEAR(order_date) = 2024;
分析:
EXPLAIN SELECT * FROM orders WHERE YEAR(order_date) = 2024;
-- type: ALL
-- Extra: Using where
优化方案:
-- 方案1:修改查询条件
SELECT * FROM orders
WHERE order_date >= '2024-01-01' AND order_date < '2025-01-01';
-- 方案2:创建函数索引(MySQL 8.0+)
CREATE INDEX idx_year ON orders((YEAR(order_date)));
问题SQL:
SELECT * FROM users ORDER BY created_at LIMIT 1000000, 10;
分析:
EXPLAIN SELECT * FROM users ORDER BY created_at LIMIT 1000000, 10;
-- type: ALL
-- Extra: Using filesort
优化方案:
-- 方案1:覆盖索引
SELECT u.* FROM users u
JOIN (SELECT id FROM users ORDER BY created_at LIMIT 1000000, 10) t
ON u.id = t.id;
-- 方案2:游标分页
SELECT * FROM users WHERE id > 1000000 ORDER BY id LIMIT 10;
问题SQL:
SELECT u.name, o.order_id
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.dept_id = 1;
分析:
EXPLAIN SELECT u.name, o.order_id FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE u.dept_id = 1;
优化方案:
-- 创建索引
CREATE INDEX idx_dept ON users(dept_id);
CREATE INDEX idx_user ON orders(user_id);
-- 优化查询
SELECT u.name, o.order_id
FROM users u FORCE INDEX (idx_dept)
LEFT JOIN orders o FORCE INDEX (idx_user) ON u.id = o.user_id
WHERE u.dept_id = 1;
✅ EXPLAIN分析SQL执行计划 ✅ 慢查询日志定位性能问题 ✅ 索引优化是关键 ✅ 查询重写提高性能 ✅ 表结构优化减少冗余 ✅ 服务器优化提升整体性能
完成本章学习后,请确认您能够: