今天正式进入了SQL优化的学习。如果说之前学习MySQL是在学习"如何用",那么今天就是在学习"如何用好"。从数据结构到索引原理,从慢查询定位到SQL调优,一整天的内容环环相扣,让我对数据库性能优化有了系统性的认知。
一、数据结构------索引的底层逻辑
课程从数据结构讲起,这是我之前学MySQL时完全忽略的视角。**数据结构的差异决定了索引的性能优劣**,理解数据结构才能真正理解为什么MySQL选择B+Tree。
从数组到红黑树
| 数据结构 | 查询复杂度 | 增删复杂度 | 特点 |
|----------|-----------|-----------|------|
| 数组 | O(1) | O(n) | 连续内存,下标访问快 |
| 链表 | O(n) | O(1) | 非连续内存,增删快 |
| 跳表 | O(log n) | O(log n) | 多级索引,Redis的zset使用 |
| 平衡二叉树AVL | O(log n) | O(log n) | 严格平衡,调整代价大 |
| 红黑树 | O(log n) | O(log n) | 弱平衡,增删性能优于AVL |
红黑树的5条规则让我印象深刻:节点非红即黑、根黑叶黑、红节点子节点必须为黑、从任一节点到叶子的路径黑色节点数相同。最后一条规则保证了**最长路径不超过最短路径的2倍**,这是红黑树"弱平衡"的核心。
B-Tree与B+Tree------数据库的真正选择
B-Tree(B树,不是B减树)是多路平衡搜索树,每个节点既存关键字也存数据,所有叶子在同一层。
B+Tree是B-Tree的变种,核心区别在于:
内部节点只存关键字和指针,不存数据------页面可以容纳更多关键字,树更矮
所有数据都存储在叶子节点------查询必须走到叶子,性能稳定
叶子节点之间用指针相连------范围查询极其高效
MySQL选择B+Tree而非B-Tree的原因:
-
磁盘I/O更少:树矮,每次IO加载更多关键字
-
范围查询更强:叶子链表支持顺序遍历
-
查询性能稳定:每次都要走到叶子层,IO次数固定
-
适合磁盘存储:节点大小与磁盘页大小匹配
二、存储引擎与索引分类
InnoDB vs MyISAM
| 对比维度 | InnoDB | MyISAM |
|----------|--------|--------|
| 事务 | ✅ 支持 | ❌ 不支持 |
| 外键 | ✅ 支持 | ❌ 不支持 |
| 锁粒度 | 行级锁 | 表锁 |
| 索引结构 | 聚集索引(数据+索引一起) | 非聚集索引(数据和索引分离) |
MySQL 5.5之后InnoDB成为默认引擎,事务支持和行级锁使其在高并发场景下表现更优。
聚集索引 vs 非聚集索引
聚集索引:数据和索引在一起,叶子节点存放完整行数据。主键就是聚集索引,没有主键则用唯一索引,都没有则自动生成隐藏列。
非聚集索引(二级索引):叶子节点存放主键值。通过非聚集索引查询需要**回表**------先查二级索引拿主键,再到聚集索引拿完整数据。
三、性能分析工具
SQL频率监控
```sql
SHOW GLOBAL STATUS LIKE 'Com______'; -- 7个下划线
查看数据库的DML操作频率,判断是否需要优化。
慢查询日志
开启慢查询日志,记录执行时间超过阈值的SQL:
properties
slow_query_log=1
long_query_time=2
SHOW PROFILES
`sql
SET profiling=1;
SHOW PROFILES;
SHOW PROFILE CPU FOR QUERY 3;
分析SQL的CPU消耗分布:
cpu_user:用户模式消耗,表示查询逻辑计算密集
cpu_system:系统模式消耗,表示I/O或系统调用密集
EXPLAIN ------ 最核心的分析工具
EXPLAIN的字段中,type是最关键的指标,性能从好到差:
NULL → system → const → eq_ref → ref → range → index → all
| type | 含义 | 优化建议 |
|------|------|----------|
| const | 主键或唯一索引查询 | ✅ 最优 |
| eq_ref | 唯一索引关联查询 | ✅ 很好 |
| ref | 非唯一索引查询 | ⚠️ 可接受 |
| range | 范围查询 | ⚠️ 可接受 |
| index | 扫描整个索引树 | ❌ 需优化 |
| all | 全表扫描 | ❌ 必须优化 |
目标是让type达到range级别以上。
四、索引使用规则
最左前缀法则
复合索引 `(name, age, major)` 中,查询条件必须包含最左列 `name` 才能使用索引。
```sql
-- ✅ 使用索引
WHERE name = '亚瑟' AND age = 20 AND major = '土木'
WHERE name = '亚瑟' AND age = 20
-- ❌ 索引失效(未使用最左列)
WHERE age = 20 AND major = '土木'
```
跨列使用会导致部分索引失效:`name` 和 `age` 之间有 `age`,跳过 `age` 直接查 `major`,只有 `name` 走索引。
范围查询
复合索引中,一旦出现 `>`、`<`、`BETWEEN`、`LIKE`(前缀模糊),**范围字段右边的所有条件全部失效**。
```sql
-- ❌ major失效(age用了范围查询)
WHERE name = '亚瑟' AND age > 20 AND major = '土木'
-- ✅ 三个字段都用上索引
WHERE name = '亚瑟' AND age >= 20 AND major = '土木'
索引失效的常见场景
-
对索引列做计算:`WHERE age + 10 = 30` → 失效
-
字符串列用数字查询:`WHERE code = 10001`(code是varchar)→ 失效
-
LIKE以%开头:`WHERE name LIKE '%亚瑟'` → 失效
-
OR导致索引失效:两边条件都有索引才可能走索引,否则全表扫描
覆盖索引
覆盖索引是指查询需要的字段都能在索引中找到,不需要回表。Extra显示`Using index`时表示使用了覆盖索引。
```sql
-- ✅ 覆盖索引(id和name都在索引中)
SELECT id, name FROM students WHERE name = '亚瑟'
-- ❌ 需要回表(major不在索引中)
SELECT id, name, major FROM students WHERE name = '亚瑟'
尽量不要用 `SELECT *`,只查需要的字段,这既是规范也是优化。
五、前缀索引
对于长字符串列(如email、长文本),建立完整索引会导致索引文件过大。**前缀索引**只对字符串的前N个字符建立索引:
```sql
CREATE INDEX email_index ON students(email(5));
选择性的概念:不重复记录数 / 总记录数,值越接近1越好。通过计算不同前缀长度的选择性,找到合适的长度。
六、SQL优化实践
INSERT优化
使用批量插入(建议一次不超过1000条)
手动提交事务(多条INSERT用START TRANSACTION + COMMIT)
主键顺序插入,避免页分裂
使用LOAD命令加载本地文件
主键设计原则
尽量降低主键长度
主键值保持不变
避免使用UUID作为主键
使用自增主键保证顺序插入
ORDER BY优化
目标是从`Using filesort`优化到`Using index`:
为排序字段建立索引
使用覆盖索引避免回表
复合索引排序时遵循最左前缀
升降序方向需与索引定义一致
GROUP BY优化
建立索引提升分组效率
遵循最左前缀法则
Extra出现`Using temporary`说明使用了临时表,需要优化
LIMIT优化
大偏移量查询慢的根本原因是MySQL需要跳过大量行:
```sql
-- ❌ 慢:需要处理100010行再抛弃100000行
SELECT * FROM students LIMIT 100000, 10
-- ✅ 优化:先查id,再关联
SELECT * FROM students s
INNER JOIN (SELECT id FROM students LIMIT 100000, 10) ss
COUNT优化
理论上效率:`count(普通列)` < `count(id)` < `count(1)` ≈ `count(*)`
实际受版本和存储引擎影响,课堂上实测`count(name)`竟然比`count(*)`更快------说明理论需要结合实际。
七、今日总结
今天的SQL优化学习让我从"能写SQL"进入了"能写好SQL"的阶段:
| 知识点 | 核心要点 |
|--------|----------|
| 数据结构 | B+Tree是MySQL索引的基石 |
| 存储引擎 | InnoDB支持事务+行锁,MyISAM只读性能好 |
| 聚集索引 | 数据+索引在一起,回表是性能杀手 |
| EXPLAIN | type是关键指标,目标是range及以上 |
| 最左前缀 | 复合索引必须包含最左列 |
| 覆盖索引 | 减少回表,Extra显示Using index |
| 前缀索引 | 长字符串列节省空间 |
三点感悟:
-
**知其然更要知其所以然**:以前只知道"索引能加速查询",今天终于理解了B+Tree的结构和MySQL选择它的原因。
-
**EXPLAIN是SQL优化的起点**:写SQL之前先用EXPLAIN看一下执行计划,type为all的SQL一定要优化。
-
索引不是越多越好:索引能加速查询,但会拖慢增删改。单表索引数建议不超过5个,复合索引设计要合理。
明天也继续加油