性能優化!3種場景下使用@Transactional對性能影響太大了
環境: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: false創建實體對象
@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
}準備接口
@GetMapping("")
public ResponseEntity<?> query() {
return ResponseEntity.ok(this.userService.queryUser()) ;
}準備數據(500w)
圖片
2.1 使用Repository查詢測試
測試1,不使用@Transactional注解
private final UserRepository userRepository ;
public User queryUser() {
return this.userRepository.findById(4888888).orElse(null) ;
}使用JMeter測試結果如下:
圖片
吞吐量平均:9700
測試2,使用@Transactional
@Transactional
public User queryUser() {}使用JMeter測試結果如下:
圖片
吞吐量平均:12700
這是否打破了你原有的認知呢?按照常規理論,使用 @Transactional 注解通常會使性能變差,然而當前呈現的數據卻表明,使用該注解后性能反而有所提升。
測試3,使用只讀事務
@Transactional(readOnly = true)
public User queryUser() {}使用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 ;
}
}) ;
}JMeter測試結果:
圖片
吞吐量平均:25000
JPA是簡單了,代價就是性能太差了。
測試2,使用@Transactional注解
@Transactional
public User queryUser() {}JMeter測試結果:
圖片
吞吐量平均:13100
這倒是符合我們的預期,使用了@Transactional注解性能明顯下降。
測試3,使用只讀事務
@Transactional(readOnly = true)
public User queryUser() {}JMeter測試結果:

同樣符合預期,與讀寫事務差不多。
2.4 使用EntityManager查詢
測試1,不使用@Transactional注解
private final EntityManager em ;
public User queryUser() {
return this.em.find(User.class, 4888888) ;
}JMeter測試結果:
圖片
吞吐量平均:24000
測試2,使用@Transactional注解
@Transactional
public User queryUser() {
return this.em.find(User.class, 4888888) ;
}JMeter測試結果:
圖片
吞吐量平均:13000
測試3,使用只讀事務
@Transactional(readOnly = true)
public User queryUser() {}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);
}




























