性能优化
query-optimization、query-analysis-techniques、reducing-subqueries。
性能优化
这一课你将能做到
- 读懂 EXPLAIN 的 type、key、rows、Extra 四个关键列,判断一条 SQL 是「走索引」还是「全表扫描」
- 亲手复现三大索引失效场景(函数包裹、前导模糊、隐式转换)并逐一改写成索引友好写法
- 按最左前缀原则设计联合索引,并背出可复用的慢 SQL 排查五步法
为什么要学
功能写完只是上线的前一半,跑得快才是后一半。一条全表扫描的 SQL 在 12 行的练习库里无感,在 1200 万行的生产表上就是拖垮整个服务的导火索。慢 SQL 优化是后端面试的绝对高频区:「索引什么时候失效」「EXPLAIN 重点看什么」「线上 SQL 突然变慢你怎么排查」——这三问的答案质量基本决定了数据库项的评分。
人话:索引是一本书的目录,EXPLAIN 是「监考老师看你翻书的方式」。全表扫描 = 从第一页翻到最后一页;走索引 = 直接翻目录跳页。优化就是让每条查询都学会「翻目录」。
先建立图像
EXPLAIN 里 type 列的性能阶梯(从好到坏):
const ─► eq_ref ─► ref ─► range ─► index ─► ALL
主键/唯一等值 JOIN被驱动表命中 普通索引等值 索引范围扫 扫全索引 扫全表
★★★★★ ★★★★ ★★★ ★★ ★ ☠
联合索引 idx_user_status(user_id, status) 的树结构:
(user_id, status) 按序排列:
(1,'accepted') (1,'wrong_answer') (2,'accepted') (2,'accepted') (5,'accepted') ...
└─ 等值 user_id 可以定位段,段内 status 有序 ─┘
└─ 但只给 status 不给 user_id?前面乱序,没法二分 ─┘ ← 最左前缀
跟着做
步骤 0:数据准备
USE sql_practice;
TRUNCATE TABLE cf_submission;
TRUNCATE TABLE cf_practice_record;
TRUNCATE TABLE cf_user;
TRUNCATE TABLE cf_problem;
-- 执行第 1 课步骤 4 的四段 INSERT 恢复初始数据
步骤 1:基线——一条全表扫描的 SQL
EXPLAIN
SELECT * FROM cf_submission
WHERE status = 'accepted' AND runtime_ms < 50;
关注四列(本库仅 12 行,rows 是估算值,思路最重要):
| type | key | rows | Extra |
|---|---|---|---|
| ALL | NULL | 12 | Using where |
type=ALL 就是全表扫描——status 和 runtime_ms 上都没有索引,只能逐行判断。
步骤 2:建立联合索引并观察变化
CREATE INDEX idx_user_status ON cf_submission (user_id, status);
EXPLAIN
SELECT * FROM cf_submission
WHERE user_id = 1 AND status = 'accepted';
预期:type 从 ALL 变成 ref,key=idx_user_status,rows 大幅缩小。等值条件命中联合索引前两列,走的是「定位 + 顺序读」。
步骤 3:最左前缀——索引在但用不上「定位」
EXPLAIN
SELECT * FROM cf_submission
WHERE status = 'accepted';
-- type=index(扫全索引,好于 ALL 但仍是全扫):联合索引跳过了最左列 user_id,
-- 无法二分定位,只能把索引整体扫一遍
结论:联合索引 (user_id, status) 只服务「带 user_id 的查询」。查询习惯决定列序,不能反过来。