警惕!@Transactional(readOnly=true) 可能带来隐藏的性能陷阱
警惕!@Transactional(readOnly=true) 可能带来隐藏的性能陷阱
前言
今天,我想谈谈 Spring 提供的@Transactional(readOnly = true)
。之所以聊这个是因为我公司项目的代码里有很多@Transactional(readOnly = true)
,用过的同学都说@Transactional(readOnly = true)
提高了性能。
先思考以下几点:
@Transactional(readOnly = true)
是如何工作的,为什么使用它可以提高性能?- 当我们使用 JPA 时,是否应该总是将
@Transactional(readOnly = true)
添加到服务层的只读方法?有什么取舍吗?
在开始之前,我们使用 Hibernate 来实现 JPA。
1. @Transactional(readOnly = true)是如何工作的,为什么使用它可以提高性能?
首先,让我们看一下事务接口。
代码语言:javascript代码运行次数:0运行复制/**
* A boolean flag that can be set to {@code true} if the transaction is
* effectively read-only, allowing for corresponding optimizati at runtime.
* <p>Defaults to {@code false}.
* <p>This just serves as a hint for the actual transaction subsystem;
* it will <i>not necessarily</i> cause failure of write access attempts.
* A transaction manager which cannot interpret the read-only hint will
* <i>not</i> throw an exception when asked for a read-only transaction
* but rather silently ignore the hint.
* @see org.interceptor.TransactionAttribute#isReadOnly()
* @see org.support.TransactionSynchronizationManager#isCurrentTransactionReadOnly()
*/
boolean readOnly() default false;
我们可以看到 readOnly = true
选项允许优化。事务管理器将使用只读选项作为提示。让我们看看用于事务管理器的JpaTransactionManager
。
@@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
JpaTransactionObject txObject = (JpaTransactionObject) transaction;
// .
// Delegate to JpaDialect for actual transaction begin.
int timeoutToUse = determineTimeout(definition);
Object transactionData = getJpaDialect().beginTransaction(em,
new JpaTransactionDefinition(definition, timeoutToUse, txObject.isewEntityManagerHolder()));
//...
}
在JpaTransactionManager
中,doBegin方法委托JpaDialect来开始实际的事务,并在JpaDialect中调用beginTransaction
。让我们来看看HibernateJpaDialect
类。
@Override
public Object beginTransaction(EntityManager entityManager, TransactionDefinition definition)
throws PersistenceException, SQLException, TransactionException {
// ...
// Adapt flush mode and store previous isolation level, if any.
FlushMode previousFlushMode = prepareFlushMode(session, definition.isReadOnly());
if (definition instanceof ResourceTransactionDefinition &&
((ResourceTransactionDefinition) definition).isLocalResource()) {
// As of 5.1, we explicitly optimize for a transaction-local EntityManager,
// aligned with native HibernateTransactionManager behavior.
previousFlushMode = null;
if (definition.isReadOnly()) {
session.setDefaultReadOnly(true);
}
}
// ...
}
protected FlushMode prepareFlushMode(Session session, boolean readOnly) throws PersistenceException {
FlushMode flushMode = session.getHibernateFlushMode();
if (readOnly) {
// We should suppress flushing for a read-only transaction.
if (!(FlushMode.MAUAL)) {
session.setHibernateFlushMode(Flusode.MAUAL);
return flushMode;
}
}
else {
// We need AUTO or COMMIT for a non-read-only transaction.
if (flushMode.lessThan(FlushMode.COMMIT)) {
session.setHibernateFlushMode(FlushMode.AUTO);
return flushMode;
}
}
// o FlushMode change needed...
return null;
}
在JpaDialect中,我们可以看到JpaDialect使用只读选项准备刷新模式。当 readOnly = true
时, JpaDialect 禁止刷新。此外,您还可以看到,在准备刷新模式后,session.setDefaultReadOnly(true)
将session的readOnly属性设置为true。
/**
* Change the default for entities and proxies loaded into this session
* from modifiable to read-only mode, or from modifiable to read-only mode.
*
* Read-only entities are not dirty-checked and snapshots of persistent
* state are not maintained. Read-only entities can be modified, but
* changes are not persisted.
*
* When a proxy is initialized, the loaded entity will have the same
* read-only/modifiable setting as the uninitialized
* proxy has, regardless of the session's current setting.
*
* To change the read-only/modifiable setting for a particular entity
* or proxy that is already in this session:
* @see Session#setReadOnly(Object,boolean)
*
* To override this session's read-only/modifiable setting for entities
* and proxies loaded by a Query:
* @see Query#setReadOnly(boolean)
*
* @param readOnly true, the default for loaded entities/proxies is read-only;
* false, the default for loaded entities/proxies is modifiable
*/
void setDefaultReadOnly(boolean readOnly);
在Session接口中,通过将readOnly属性设置为true,将不会对只读实体进行脏检查,也不会维护持久状态的快照。此外,只读实体的更改也不会持久化。总而言之,这些是在 Hibernate 中使用@Transactional(readOnly = true)
所得到的结果
- 性能改进:只读实体不进行脏检查
- 节省内存:不维护持久状态的快照
- 数据一致性:只读实体的更改不会持久化
- 当我们使用主从或读写副本集(或集)时,
@Transactional(readOnly = true)
使我们能够连接到只读数据库
2.当我们使用 JPA 时,是否应该总是将@Transactional(readOnly = true)添加到服务层的只读方法?有什么取舍吗?
我看到,当使用@Transactional(readOnly = true)
时,我们可以有很多优势。但是,将@Transactional(readOnly = true)
添加到服务层的只读方法是否合适?以下是我担心的事情
- 无限制地使用事务可能会导致数据库死锁、性能和吞吐量下降。
- 由于一个事务占用一个DB连接,所以
@Transactional(readOnly = true)
添加到Service层的方法可能会导致DB连接饥饿。
第一个问题很难重现,所以我做了一些测试来检查第二个问题。
代码语言:javascript代码运行次数:0运行复制@Transactional(readOnly = true)
public List<UserDto> transactionalReadOnlyOnService(){
List<UserDto> userDtos = userRepository.findAll().stream()
.map(userMapper::toDto)
.toList();
timeSleepAndPrintConnection();
return userDtos;
}
public List<UserDto> transactionalReadOnlyOnRepository(){
List<UserDto> userDtos = userRepository.findAll().stream()
.map(userMapper::toDto)
.toList();
timeSleepAndPrintConnection();
return userDtos;
}
我在服务层测试了两个方法,一个是@Transactional(readOnly = true)
,另一个是存储库层中的@Transactional (readOnly = true)
(在 SimpleJpaRepository
中,它是 Jpa Respitory 的默认实现,在类的顶部有@Transformational(ready Only)
,因此 findAll()
方法在默认情况下有@transactional(read only = True)
)。我从DB中获取userInfo并保持线程5秒钟,然后检查该方法何时释放连接。结果如下:对于服务层方法中的@Transactional(readOnly = true)
,
ctiveConnecti:0, IdleConnecti:10, TotalConnecti:10
start transactionalReadOnlyOnService!!
Hibernate:
select
u1_0.id,
u1_,
u1_,
u1_0.profile_file_name
from
users u1_0
activeConnecti:1, IdleConnecti:9, TotalConnecti:10
activeConnecti:1, IdleConnecti:9, TotalConnecti:10
activeConnecti:1, IdleConnecti:9, TotalConnecti:10
activeConnecti:1, IdleConnecti:9, TotalConnecti:10
activeConnecti:1, IdleConnecti:9, TotalConnecti:10
end transactionalReadOnlyOnService!!
activeConnecti:0, IdleConnecti:10, TotalConnecti:10
对于存储库层方法中的@Transactional(readOnly = true)
,
activeConnecti:0, IdleConnecti:10, TotalConnecti:10
start transactionalReadOnlyOnRepository!!
Hibernate:
select
u1_0.id,
u1_,
u1_,
u1_0.profile_file_name
from
users u1_0
activeConnecti:0, IdleConnecti:10, TotalConnecti:10
activeConnecti:0, IdleConnecti:10, TotalConnecti:10
activeConnecti:0, IdleConnecti:10, TotalConnecti:10
activeConnecti:0, IdleConnecti:10, TotalConnecti:10
activeConnecti:0, IdleConnecti:10, TotalConnecti:10
end transactionalReadOnlyOnRepository!!
activeConnecti:0, IdleConnecti:10, TotalConnecti:10
正如您所看到的,@Transactional(readOnly = true)
一旦查询结果到达,存储库层就会释放连接。然而,@Transactional(readOnly = true)
在服务层的方法中直到服务层的方法结束才释放连接。因此,当服务层的方法有需要大量时间的逻辑时要小心,因为它可以长时间持有数据库连接,这可能会导致数据库连接匮乏。
. 回顾
很明显,@Transactional(readOnly = true)
有很多优点。
- 性能改进:只读实体不进行脏检查
- 节省内存:不维护持久状态的快照
- 数据一致性:只读实体的更改不会持久化
- 当我们使用主从或读写副本集(或集)时,
@Transactional(readOnly = true)
使我们能够连接到只读数据库
但是,您还应该记住,@Transactional(readOnly = true)
在服务层的方法中可能会导致数据库死锁、性能低下和数据库连接匮乏!当您需要将只读查询仅仅作为一个事务执行时,请毫不犹豫选择的在服务层的方法中使用@Transactional(readOnly = true)
,如果你的服务层的方法中有大量其他逻辑方法时,就要做取舍了!
#感谢您对电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格的认可,转载请说明来源于"电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格
留言与评论(共有 13 条评论) |
本站网友 成都装饰 | 11分钟前 发表 |
u1_0.profile_file_name from users u1_0 activeConnecti | |
本站网友 死肌肉 | 8分钟前 发表 |
在类的顶部有@Transformational(ready Only) | |
本站网友 腊梅花的功效 | 2分钟前 发表 |
您还可以看到 | |
本站网友 上海刺青 | 3分钟前 发表 |
if any. FlushMode previousFlushMode = prepareFlushMode(session | |
本站网友 波浪 | 23分钟前 发表 |
TotalConnecti | |
本站网友 小孩第一次发烧很重要 | 2分钟前 发表 |
or from modifiable to read-only mode. * * Read-only entities are not dirty-checked and snapshots of persistent * state are not maintained. Read-only entities can be modified | |
本站网友 覃迅云 | 15分钟前 发表 |
性能和吞吐量下降 | |
本站网友 厦门建发集团 | 16分钟前 发表 |
// aligned with native HibernateTransactionManager behavior. previousFlushMode = null; if (definition.isReadOnly()) { session.setDefaultReadOnly(true); } } // ... } protected FlushMode prepareFlushMode(Session session | |
本站网友 mac输入法切换快捷键 | 7分钟前 发表 |
如果你的服务层的方法中有大量其他逻辑方法时 | |
本站网友 安格奖金制度 | 17分钟前 发表 |
TransactionException { // ... // Adapt flush mode and store previous isolation level | |
本站网友 肥皂掉了 | 23分钟前 发表 |
TransactionException { // ... // Adapt flush mode and store previous isolation level | |
本站网友 纯真岁月家具 | 2分钟前 发表 |
第一个问题很难重现 |