环境:SpringBoot3.4.2 1. 简介@Transactional 是性能性 Spring 中用于声明式事务管理的核心注解,旨在简化数据库事务操作。优化影响在传统的种场编程式事务中,我们需手动编写事务的景下开启、提交或回滚代码,使用而通过 @Transactional 注解将事务逻辑与业务代码解耦。性能性只需在方法或类上添加该注解,优化影响Spring 会基于 AOP(面向切面编程)自动拦截调用,种场在方法执行前开启事务,景下执行后根据异常情况提交或回滚。使用这种设计显著提升了代码的性能性可读性和可维护性。 但如果滥用@Transactional,优化影响会对系统性能产生显著负面影响,种场主要体现在以下几个方面: 过度使用会导致事务范围过大,景下延长数据库连接占用时间,使用增加锁竞争和死锁风险不必要的细粒度事务会引发频繁的提交和回滚操作,加重数据库负载在非关键数据操作或只读场景中滥用事务,会无谓消耗系统资源,降低整体吞吐量。站群服务器本篇文章会介绍基于 JPA 和 JDBC 时,@Transactional 注解对查询性能的影响。 纯查询到底要不要事务? 2.实战案例2.1 准备环境配置文件 复制spring: datasource: driverClassName: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/batch username: root password: 123123 type: com.zaxxer.hikari.HikariDataSource hikari: minimumIdle: 10 maximumPoolSize: 10 --- spring: jpa: generateDdl: false hibernate: ddlAuto: update openInView: true show-sql: false1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18. 创建实体对象 复制@Entity @Table(name = "o_user") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id ; private String name ; private Integer age ; private String phone ; private String sex ; // getters, setters }1.2.3.4.5.6.7.8.9.10.11.12. 准备接口 复制@GetMapping("") public ResponseEntity<?> query() { return ResponseEntity.ok(this.userService.queryUser()) ; }1.2.3.4. 准备数据(500w) 图片 2.2 使用Repository查询测试 测试1,不使用@Transactional注解 复制private final UserRepository userRepository ; public User queryUser() { return this.userRepository.findById(4888888).orElse(null) ; }1.2.3.4. 使用JMeter测试结果如下: 图片
吞吐量平均:9700 测试2,使用@Transactional 复制@Transactional public User queryUser() {}1.2. 使用JMeter测试结果如下: 图片
吞吐量平均:12700 这是否打破了你原有的认知呢?按照常规理论,使用 @Transactional 注解通常会使性能变差,然而当前呈现的数据却表明,使用该注解后性能反而有所提升。 测试3,使用只读事务 复制@Transactional(readOnly = true) public User queryUser() {}1.2. 使用JMeter测试结果如下: 图片
吞吐量平均:9300 该结果与不使用注解相差不大。 思考:为什么使用了@Transactional注解反而性能更高呢?欢迎大家留言讨论。 2.3 使用JDBC查询 测试1,不使用@Transactional注解 复制private final JdbcTemplate jdbcTemplate ; public User queryUser() { return this.jdbcTemplate.queryForObject("select id, name, age, phone, sex from o_user where id = 4888888", new RowMapper<User>() { @Override public User mapRow(ResultSet rs, int rowNum) throws SQLException { User user = new User() ; user.setId(rs.getInt("id")) ; user.setAge(rs.getInt("age")) ; user.setName(rs.getString("name")) ; user.setPhone(rs.getString("phone")) ; user.setSex(rs.getString("sex")) ; return user ; } }) ; }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15. JMeter测试结果: 图片
吞吐量平均:25000 JPA是简单了,代价就是性能太差了。 测试2,使用@Transactional注解 复制@Transactional public User queryUser() {}1.2. JMeter测试结果: 
吞吐量平均:13100 这倒是符合我们的预期,使用了@Transactional注解性能明显下降。免费信息发布网 测试3,使用只读事务 复制@Transactional(readOnly = true) public User queryUser() {}1.2. JMeter测试结果: 图片
同样符合预期,与读写事务差不多。 2.4 使用EntityManager查询 测试1,不使用@Transactional注解 复制private final EntityManager em ; public User queryUser() { return this.em.find(User.class, 4888888) ; }1.2.3.4. JMeter测试结果: 图片
吞吐量平均:24000 测试2,使用@Transactional注解 复制@Transactional public User queryUser() { return this.em.find(User.class, 4888888) ; }1.2.3.4. JMeter测试结果: 图片
吞吐量平均:13000 测试3,使用只读事务 复制@Transactional(readOnly = true) public User queryUser() {}1.2. JMeter测试结果: 图片
吞吐量平均:9800 2.5 性能柱状图 图片 2.6 查询使用事务总结保证一致性:在一个事务中,所有查询看到的是同一时间点的数据快照(取决于隔离级别),避免了中途数据被其他事务修改导致的不一致性能优化:Spring 提供 @Transactional(readOnly = true),明确标记为只读事务。这可以让底层数据库(如 Oracle、MySQL InnoDB)进行优化,例如启用只读快照、减少锁竞争等。连接复用:在一个事务中的多个操作可以复用同一个数据库连接,减少连接创建/释放开销。与写操作兼容:如果将来该查询方法被包含在一个更大的写事务中,有 @Transactional 可以无缝集成。 如下多个查询使用事务保证了同一时间点的香港云服务器数据: 复制private final UserRepository userRepository ; private final OrderRepository orderRepository ; @Transactional(readOnly = true) public UserInfoDto getUserInfo(Long userId) { User user = userRepository.findById(userId); List<Order> orders = orderRepository.findByUserId(userId); return new UserInfoDto(user, orders); }1.2.3.4.5.6.7.8.9.10. |