做Java后端开发这几年,手上管过不少Spring Boot项目,从0到1搭过,也接手过上线后响应越来越慢的”祖传系统”。今天来总结一下实际项目中常见的性能瓶颈,以及我是怎么排查和解决的。
坑一:数据库连接池配了个寂寞
最常见的坑之一。默认HikariCP最大连接数只有10,但项目QPS一上来就傻眼了。
# 典型错误配置
spring:
datasource:
hikari:
maximum-pool-size: 10 # 太小了!
# 推荐配置(根据服务器配置调整)
spring:
datasource:
hikari:
maximum-pool-size: 30
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
怎么判断连接池太小? 看Hikari日志里的 Wait count,如果等待获取连接的次数持续>0,就得加连接数了。
坑二:N+1查询——一条SQL引发的连环血案
用过MyBatis-Plus或者JPA的都很容易踩这个坑。
// 问题代码:查出100个用户,然后每个用户再查一次角色
List<User> users = userMapper.selectList(null);
users.forEach(u -> {
u.setRoles(roleMapper.selectByUserId(u.getId())); // N+1!
});
// 正确写法:JOIN一次查出来
@Select("SELECT u.*, r.name as role_name FROM user u " +
"LEFT JOIN role r ON u.id = r.user_id")
List<User> usersWithRoles();
坑三:@Async异步注解用错了
@Async看起来很美,但异步方法内部调用同类方法是失效的,因为Spring的代理机制根本吃不到。
@Service
public class OrderService {
// 同类调用,@Async失效!还是同步执行的
public void createOrder(Order order) {
this.sendNotification(order); // 同步执行,没卵用
}
// 正确姿势:注入自己,或者用ApplicationEventPublisher
@Autowired
private OrderService self;
public void createOrder(Order order) {
self.sendNotification(order); // 走代理了,真正异步
}
@Async
public void sendNotification(Order order) {
// 异步发送通知
}
}
坑四:@Transactional注解踩坑
默认情况下,所有public方法都会在同一个事务里。有人在事务方法里调了HTTP请求或者大查询,整个事务就拖得很长,数据库连接一直被占用。
@Transactional
public void processOrder(Long orderId) {
Order order = orderMapper.selectById(orderId);
// 千万别在这里发HTTP请求或做大查询!!!
// 应该先查好数据,再进事务处理逻辑
restTemplate.getForObject("http://third-party/api", String.class); // 危险!
orderMapper.updateById(order);
}
坑五:循环里查数据库
// 问题代码
for (Long userId : userIds) {
User user = userMapper.selectById(userId); // 循环查库,100个用户=100次DB
}
// 正确写法:批量查询
List<User> users = userMapper.selectBatchIds(userIds);
总结:排查思路
- 响应慢先看日志,找哪个环节耗时长
- 加Spring Boot Actuator监控:
/actuator/metrics/hikari.pool.Wait - 开启MyBatis-Plus SQL日志,确认有没有N+1
- Profiler工具(Arthas/JProfiler)找CPU耗时热点
- 数据库慢查询日志(
slow_query_log=1)
性能优化是个长期工程,核心原则是:先定位,再优化,不要凭感觉改代码。
⚠️ 声明:本文相关内容仅供参考。