Spring Boot项目越写越慢?我踩了这些性能调优的坑

做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

性能优化是个长期工程,核心原则是:先定位,再优化,不要凭感觉改代码

⚠️ 声明:本文相关内容仅供参考。

发表评论

苏ICP备18039580号-2