MySQL SQL调优详解:explain执行计划、索引失效、慢查询优化一条龙

一、MySQL架构

1.1 MySQL逻辑架构

复制代码
┌──────────────────────────────────────┐
│           客户端层(连接处理)         │
└─────────────────┬────────────────────┘
                  │
┌─────────────────┴────────────────────┐
│           Server层(SQL处理)        │
│  ┌─────────┐ ┌─────────┐ ┌────────┐ │
│  │ 连接器  │ │ 解析器  │ │ 优化器 │ │
│  └─────────┘ └─────────┘ └────────┘ │
└─────────────────┬────────────────────┘
                  │
┌─────────────────┴────────────────────┐
│           存储引擎层(InnoDB/MyISAM) │
└──────────────────────────────────────┘

1.2 InnoDB vs MyISAM

特性 InnoDB MyISAM
事务 支持 不支持
外键 支持 不支持
锁粒度 行锁 表锁
全文索引 5.6+支持 支持
崩溃恢复 支持 不支持

二、索引详解

2.1 索引数据结构

B+ Tree索引:

复制代码
        [15]                ← 索引页
       /    \
   [5,10]  [20,25]          ← 索引页
   / | \   / | \
  叶  叶  叶  叶  叶  叶      ← 数据页
  ↓  ↓  ↓  ↓  ↓  ↓
 实际数据,顺序链表连接

B+ Tree vs B Tree:

对比 B Tree B+ Tree
数据存储 所有节点 仅叶子节点
查询稳定性 不稳定 所有查询复杂度相同
范围查询 需要回旋 叶子节点链表支持

2.2 索引分类

类型 说明 示例
主键索引 主键自动建 PRIMARY KEY(id)
唯一索引 唯一不重复 UNIQUE(name)
普通索引 普通加速查找 INDEX(name)
联合索引 多列组合 INDEX(a,b,c)
全文索引 文本搜索 MATCH(content) AGAINST('关键词')

三、Explain执行计划

3.1 explain使用

sql 复制代码
EXPLAIN SELECT * FROM user WHERE name = '张三';

输出字段:

字段 含义
id 查询序号
select_type 查询类型
table 表名
type 访问类型
possible_keys 可用索引
key 实际使用索引
key_len 索引长度
ref 索引引用
rows 扫描行数估算
Extra 额外信息

3.2 type详解(从好到差)

type值 说明 案例
system 表只有一行 系统表
const 最多一行 PRIMARY KEY
eq_ref 唯一扫描 PRIMARY KEY/UNIQUE
ref 非唯一扫描 普通索引
range 范围扫描 BETWEEN/IN/LIKE
index 全索引扫描 INDEX
ALL 全表扫描 无索引

3.3 Extra详解

含义
Using filesort 需要额外排序(不好)
Using temporary 需要临时表(不好)
Using index 覆盖索引(好)
Using index condition 索引下推(好)
Using where 回表过滤

四、索引失效场景

4.1 索引失效的11种情况

sql 复制代码
-- 1. 索引列参与计算
EXPLAIN SELECT * FROM user WHERE age + 1 = 30;
-- ✖ 失效

-- 2. 索引列使用函数
EXPLAIN SELECT * FROM user WHERE SUBSTRING(name, 1, 3) = '张三';
-- ✖ 失效

-- 3. 类型转换
EXPLAIN SELECT * FROM user WHERE age = '30';
-- ✓ 有效(MySQL会自动转换)

EXPLAIN SELECT * FROM user WHERE name = 123;
-- ✖ 失效(字符串字段用数字查)

-- 4. LIKE以%开头
EXPLAIN SELECT * FROM user WHERE name LIKE '%三';
-- ✖ 失效

EXPLAIN SELECT * FROM user WHERE name LIKE '张%';
-- ✓ 有效

-- 5. OR前后不都是索引列
EXPLAIN SELECT * FROM user WHERE name = '张三' OR age = 30;
-- ✖ 失效(age不是索引)

-- 6. NOT IN / NOT EXISTS
EXPLAIN SELECT * FROM user WHERE age NOT IN (20, 30);
-- ✖ 可能失效

-- 7. != / <>
EXPLAIN SELECT * FROM user WHERE age != 30;
-- ✖ 可能失效

-- 8. 联合索引违反最左前缀原则
CREATE INDEX idx_name_age ON user(name, age);

EXPLAIN SELECT * FROM user WHERE name = '张三';
-- ✓ 有效

EXPLAIN SELECT * FROM user WHERE age = 30;
-- ✖ 失效

EXPLAIN SELECT * FROM user WHERE name = '张三' AND age = 30;
-- ✓ 有效

-- 9. 排序时索引失效
CREATE INDEX idx_name_age ON user(name, age);

EXPLAIN SELECT * FROM user ORDER BY name;
-- ✓ 有效

EXPLAIN SELECT * FROM user ORDER BY age;
-- ✖ 失效

EXPLAIN SELECT * FROM user ORDER BY name, age;
-- ✓ 有效

五、慢查询优化

5.1 开启慢查询日志

sql 复制代码
-- 查看慢查询开关
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time%';

-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;

-- 查看慢查询日志
SHOW GLOBAL STATUS LIKE 'Slow_queries%';

5.2 my.cnf配置

ini 复制代码
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1

5.3 慢查询分析工具

bash 复制代码
# 使用mysqldumpslow分析
mysqldumpslow -t 10 /var/log/mysql/slow.log

5.4 慢查询优化案例

优化前:

sql 复制代码
SELECT * 
FROM orders o, users u, products p
WHERE o.user_id = u.id 
  AND o.product_id = p.id
  AND o.create_time > '2024-01-01'
ORDER BY o.create_time DESC
LIMIT 100;

优化步骤:

Step1:添加必要索引

sql 复制代码
ALTER TABLE orders ADD INDEX idx_create_time(create_time);
ALTER TABLE orders ADD INDEX idx_user_id(user_id);
ALTER TABLE orders ADD INDEX idx_product_id(product_id);

Step2:只查必要字段

sql 复制代码
SELECT o.id, o.amount, o.create_time,
       u.name as user_name,
       p.name as product_name
FROM orders o
INNER JOIN users u ON o.user_id = u.id
INNER JOIN products p ON o.product_id = p.id
WHERE o.create_time > '2024-01-01'
ORDER BY o.create_time DESC
LIMIT 100;

Step3:创建覆盖索引

sql 复制代码
CREATE INDEX idx_cover ON orders(create_time, user_id, product_id, id, amount);

六、分页优化

6.1 普通分页(大数据量会慢)

sql 复制代码
SELECT * FROM orders LIMIT 1000000, 10;
-- 偏移量越大越慢

6.2 优化分页(使用ID)

sql 复制代码
-- 第一页
SELECT * FROM orders ORDER BY id LIMIT 10;

-- 下一页:记住上一页最后一条ID
SELECT * FROM orders 
WHERE id > 1000000 
ORDER BY id 
LIMIT 10;
-- 利用主键索引,极快

七、SQL优化经验总结

7.1 常用优化规则

规则 说明
SELECT只查需要的字段 减少网络传输,使用覆盖索引
避免SELECT * 无法使用覆盖索引
批量插入替代循环单条 减少网络开销
合理使用LIMIT 分页查询要带上上一页ID
索引列不能参与运算 会导致索引失效
避免隐式类型转换 字符串字段不要用数字比较

7.2 常用优化案例

JOIN优化:

sql 复制代码
-- 优化前:小表驱动大表没做好
SELECT * FROM big_table t1 
LEFT JOIN small_table t2 ON t1.id = t2.t1_id;

-- 优化后:让小表驱动大表
SELECT * FROM small_table t2 
LEFT JOIN big_table t1 ON t1.id = t2.t1_id;

IN优化:

sql 复制代码
-- 优化前:IN中数据量大
SELECT * FROM user WHERE id IN (1,2,3,...,10000);
-- 可能变成嵌套查询

-- 优化后:先查出来再JOIN
SELECT t1.* FROM user t1 
INNER JOIN (SELECT id FROM user LIMIT 10000) t2 
ON t1.id = t2.id;

总结

知识点 说明
执行计划 type/Extra/Key是核心
索引失效 最左前缀/函数/OR/LIKE%开头
慢查询 开启慢日志+explain分析
优化方向 索引+SQL+分页+JOIN顺序

核心口诀:EXPLAIN先行,索引失效要记清,SQL优化三原则, LIMIT分页要记牢。


相关推荐
闪电悠米1 天前
黑马点评-秒杀优化-01_async_seckill_idea
java·数据库·ide·redis·分布式·缓存·intellij-idea
TDengine (老段)1 天前
TDengine 数据修复与迁移 — VGroup 调度、S3 外挂与运维操作
大数据·运维·数据库·物联网·时序数据库·iot·tdengine
努力努力再努力wz1 天前
【Qt入门系列】一文掌握 Qt 常用显示类控件:QLCDNumber、QProgressBar 与 QCalendarWidget
c语言·开发语言·数据结构·数据库·c++·git·qt
KaiwuDB1 天前
KaiwuDB 开源校园行扬州大学站 | 点亮开源成长之路
数据库·开源
玫幽倩1 天前
2026盘古石取证决赛(APK取证)
数据库·python·电子取证·aes·隐藏·笔记软件·手机取证
Navicat中国1 天前
如何在 DBA 团队中管理共享查询库
数据库·dba
兰令水1 天前
【agent第3篇】agent上下文+面经
java·大数据·数据库
*neverGiveUp*1 天前
PostgreSql常用SQL大全
数据库·sql·postgresql
流星白龙1 天前
【MySQL高阶】21.撤销表空间,撤销日志
android·mysql·adb
真实的菜1 天前
Redis 从入门到精通(三):持久化机制 —— RDB 与 AOF 深度解析
数据库·redis·缓存