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分页要记牢。


相关推荐
m0_613856292 小时前
mysql如何使用IF函数_mysql简单二元逻辑转换
jvm·数据库·python
_F_y2 小时前
SQLite3的基础使用
jvm·数据库·sqlite
XSKY星辰天合2 小时前
XSKY 与平凯星辰(TiDB)完成联合解决方案互认证,存储+数据库联合交付能力再获验证
数据库·存储
阿丰资源2 小时前
基于SpringBoot+MySQL+Maven+Vue的旅游网站的设计与实现(源码+数据库+文档一键运行)
数据库·spring boot·mysql
pele2 小时前
如何在 Go 项目中安全、高效地共享 MySQL 数据库连接
jvm·数据库·python
qq_342295822 小时前
SQL如何用SQL子查询查找最大值对应行_关联主键优化方案
jvm·数据库·python
m0_743623922 小时前
golang如何使用iota常量生成器_golang iota常量生成器使用教程
jvm·数据库·python
字节高级特工2 小时前
迈入Redis:持久化
数据库·redis·缓存
baidu_340998822 小时前
mysql如何排查连接数爆满原因_mysql show processlist分析
jvm·数据库·python