目标:
不是讲概念,而是演示一次 真实的性能优化过程 :
如何从一个 2 秒接口,优化到 90ms,并且每一步都有依据。
场景设定
接口:
java
GET /users?page=1&size=20
表数据量:50 万条
技术栈:Spring Boot + MySQL + Redis
初始状态(问题版本)
初始代码问题点
1)SQL 问题
java
SELECT * FROM user;
- 无索引
- 全表扫描
- 返回 50 万行
2)接口问题
- DTO 字段 30+
- 返回整个 List
- 打印全部日志
3)RPC 问题
java
for(User u : list){
rpcService.getScore(u.getId());
}
典型 N+1 调用
初始压测结果
| 指标 | 数值 |
|---|---|
| P50 | 300ms |
| P90 | 1200ms |
| P99 | 2100ms |
| CPU | 35% |
| 内存 | 稳定 |
结论:不是资源打满,是逻辑慢。
第一步:SQL 优化(最大头)
动作
1)加索引
java
CREATE INDEX idx_user_status_time
ON user(status, create_time);
2)分页 + 字段裁剪
java
SELECT id,name,status
FROM user
WHERE status=1
ORDER BY create_time DESC
LIMIT 20;
3)Explain 对比
| 优化前 | 优化后 |
|---|---|
| type=ALL | type=ref |
| rows=500000 | rows=120 |
SQL 优化后压测
| 指标 | 数值 |
|---|---|
| P99 | 2100 → 420ms |
第二步:接口层优化
问题点
- 返回字段过多
- 日志拖慢
- 序列化重
动作
DTO 裁剪
java
class UserDTO {
Long id;
String name;
}
日志采样
java
if(random()<0.01){
log.info("users:{}", list.size());
}
接口层优化后
| 指标 | 数值 |
|---|---|
| P99 | 420 → 180ms |
第三步:RPC 优化(N+1 问题)
原始
java
for(User u : list){
rpc.getScore(u.id);
}
改为批量
java
rpc.batchGetScore(ids);
RPC 优化后
| 指标 | 数值 |
|---|---|
| P99 | 180 → 130ms |
第四步:JVM 优化
观察指标
- Minor GC 频繁
- 对象创建多
动作
- 复用集合
- 减少字符串拼接
- 调整线程池
JVM 优化后
| 指标 | 数值 |
|---|---|
| P99 | 130 → 90ms |
| GC 次数 | -40% |
全过程对比
| 阶段 | P99 |
|---|---|
| 初始 | 2100ms |
| SQL 优化 | 420ms |
| 接口优化 | 180ms |
| RPC 优化 | 130ms |
| JVM 优化 | 90ms |
优化核心逻辑总结
java
先 SQL
再 接口
再 RPC
最后 JVM
实战中的关键判断
| 现象 | 判断 |
|---|---|
| CPU 不高却慢 | 逻辑慢 |
| P99 高 | 极端请求问题 |
| SQL rows 巨大 | 索引问题 |
| GC 多 | 对象创建问题 |
工程经验总结
不要一上来做的事
- 不要先加缓存
- 不要先调 JVM 参数
- 不要先加 MQ
应该先做的事
- 看 P99
- Explain SQL
- 拆链路
- 再优化
最终一句话
性能优化不是"调参",而是"定位 → 分层 → 验证"的工程流程。
当你能把一个 2 秒接口优化到 90ms,
你就已经具备 中高级后端工程师的实战能力 了。