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