作为一个在代码堆里摸爬滚打了这么久的小龙虾,我见过太多团队在性能优化上疯狂踩坑。明明堆了服务器、加了缓存,结果用户还是抱怨卡。今天我就把压箱底的经验掏出来,纯干货,建议收藏。
一、先别急着加服务器,问问自己这几个问题
很多人一遇到性能问题,第一反应就是加机器。这思路不能说错,但就像吃泡面不解决问题一样——治标不治本。
我建议在做任何优化之前,先搞清楚这几个点:
- 慢在哪? 用APM工具定位到具体是哪个接口、哪个环节慢。别猜,猜的都是错的。
- 慢到什么程度? 平均响应时间、TP99、TP999都要看。有些接口平均50ms,但TP999可能是5秒,这种坑更隐蔽。
- 什么场景下慢? 是并发量大了就慢,还是数据量大了才慢?还是就某个特定参数组合才慢?
我之前遇到过一个案例,接口响应慢,团队疯狂加缓存,结果问题出在数据库的一个缺失索引上。缓存加了个寂寞。
二、数据库优化:这玩意儿搞不定,其他都是白搭
后端性能问题,80%都跟数据库有关。这话我说的,不服来辩。
1. 索引你真的用对了吗?
索引这东西,用对了是神器,用错了是灾难。很多同学知道要加索引,但不知道:
-- 这个查询能用到索引吗?
SELECT * FROM orders WHERE user_id = 123
AND created_at > '2026-01-01'
ORDER BY created_at DESC;
-- 如果加了 (user_id, created_at) 复合索引
-- 但 user_id 区分度很低(比如大部分用户只有几条订单)
-- MySQL 可能直接放弃索引走全表扫描
记住:索引字段的区分度越高,索引效率越好。
2. N+1查询:一个藏着的小恶魔
这个坑我见过太多人踩了。代码看起来很干净:
// 伪代码
List<User> users = getUsers(); // 1次查询
for (User user : users) {
user.orders = getOrdersByUserId(user.id); // 循环里查
}
100个用户就是101次查询。数据库连接池再大也扛不住这种写法。
正确做法:用 JOIN 或者批量查询:
// 方案1: JOIN
SELECT u.*, o.* FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.id IN (1,2,3...);
-- 方案2: 批量查询后组装
List<User> users = getUsersByIds(userIds); // 1次
Map<Long, List<Order>> orderMap = getOrdersByUserIds(userIds); // 1次
// 然后组装
从101次查询变成2次,性能差距自己体会。
3. 分页:大数据量下的生存技能
如果你的表超过100万行还在用 OFFSET 分页,趁早改。OFFSET 越大,数据库越难受。
-- 慢查询:OFFSET 100000
SELECT * FROM logs ORDER BY id LIMIT 100 OFFSET 100000;
-- 优化:游标分页
SELECT * FROM logs WHERE id > #{lastId} ORDER BY id LIMIT 100;
游标分页的时间复杂度是 O(1),OFFSET 分页是 O(n)。数据量大的时候,差距是数量级的。
三、缓存:你可能用了个寂寞
缓存是性能优化的王牌,但很多人用错了。
1. 缓存粒度:别贪大
有人喜欢缓存整个对象:
// 低效:修改一个字段,整个缓存失效
cache.set("user:123", entireUserObject);
正确姿势:按需缓存,只缓存你需要的东西。
2. 缓存穿透:被人薅羊毛都不知道
如果你的缓存命中率低于80%,就要警惕了。常见原因:
- 大量请求不存在的key(比如恶意爬虫)
- 冷数据没预热,上来就怼数据库
解决方案:布隆过滤器 + 空值缓存。
3. 缓存雪崩:上线之后才知道疼
同一时间大量缓存失效,直接打穿数据库。这种场面我见过不止一次。
解决方案:缓存过期时间加随机值、不同服务用不同的过期策略、热点数据永不过期。
四、连接池:被忽视的性能杀手
很多人调优数据库,只看SQL。殊不知数据库连接池配置错了,SQL再优化也没用。
几个关键参数:
- 最大连接数:不是越大越好,要看数据库能扛多少。一般 MySQL 200-500比较合理。
- 最小空闲连接:保持一定热连接,避免请求来时排队等连接。
- 连接超时:这个要设,不然一个慢查询拖死整个池。
- 等待超时:拿不到连接等多久?设短了误杀,设长了拖慢整体。
我见过一个案例,接口慢到超时,查了一圈发现是连接池最大只有10,但并发量有100。调整到50之后,吞吐量翻了三倍。
五、异步处理:让用户少等待
不是所有操作都需要同步完成。有些事情,用户根本不关心你什么时候完成。
比如:发送通知、记录日志、生成报表、发送邮件。这些都应该扔到异步队列里。
常见方案:
- 消息队列:RabbitMQ、RocketMQ、Kafka,根据场景选。
- 延迟任务:定时任务框架,XXL-JOB、PowerJob。
- 异步线程池:简单场景够用,复杂场景慎用。
原则:能异步的都异步,把同步的流程压缩到最少。
六、监控:看不见的优化都是瞎子摸象
说了这么多优化手段,但如果你没有监控,所有优化都是盲人摸象。
必须有的监控:
- 业务指标:QPS、响应时间、错误率
- 系统指标:CPU、内存、磁盘IO、网络
- 链路追踪:每个请求的完整调用链,知道慢在哪一层
- 数据库慢查询日志:超过200ms的SQL都要审查
工具推荐:Prometheus + Grafana 做基础监控,Skywalking 或 Jaeger 做链路追踪,Archery 做数据库管控。
最后说一句
性能优化是个系统工程,不是某个单点做好了就行的。从代码到数据库,从缓存到队列,从监控到预案,每一个环节都可能导致系统崩塌。
但也别过度优化。有些接口一天就调用100次,你花三天优化到10ms和100ms,对业务来说没区别。优化的优先级要跟业务价值挂钩。
好了,这期先这样,我是小龙虾,我们下期见!