简体   繁体   English

Spring Boot/HikariCP @Transactional 不覆盖隔离级别

[英]Spring Boot/HikariCP @Transactional not overwriting isolation level

My application is experiencing lock contentions on one of our heavily-trafficked tables in our SQL Server database.我的应用程序在我们的 SQL 服务器数据库中的一个繁忙的表上遇到锁争用。 I have been advised by our DBA team to follow other teams' configuration, which have their default transaction isolation level set to READ_UNCOMMITTED.我们的 DBA 团队建议我遵循其他团队的配置,这些配置的默认事务隔离级别设置为 READ_UNCOMMITTED。 They then, supposedly, set the isolation level back to READ_COMMITTED for their inserts and updates.然后,据推测,他们将隔离级别设置回 READ_COMMITTED 以进行插入和更新。 I've fought against doing this for a while, as it feels like a cop-out, and I've seen warnings all over the place against using READ_UNCOMMITTED.一段时间以来,我一直反对这样做,因为这感觉像是逃避,而且我已经看到到处都是反对使用 READ_UNCOMMITTED 的警告。 However, my hands are now being tied.然而,我的手现在被绑住了。

I'm using Spring Boot, with HikariCP and using Spring Data repositories to interact with my SQL Server database.我正在使用 Spring Boot 和 HikariCP,并使用 Spring 数据存储库与我的 SQL 服务器数据库进行交互。 I'm allowing Spring to auto-configure my DataSource from my application.properties, and have very little other configuration.我允许 Spring 从我的 application.properties 自动配置我的数据源,并且几乎没有其他配置。

I have managed to set my default transaction isolation level as follows in my app properties:我已经设法在我的应用程序属性中设置我的默认事务隔离级别,如下所示:

spring.datasource.hikari.transaction-isolation=TRANSACTION_READ_UNCOMMITTED

I've been able to verify that this is working by querying the transaction log, taking the SPID from the transaction entry, and running the following query, which now returns "ReadUncommitted":我已经能够通过查询事务日志、从事务条目中获取 SPID 并运行以下查询来验证它是否正常工作,该查询现在返回“ReadUncommitted”:

SELECT CASE transaction_isolation_level 
WHEN 0 THEN 'Unspecified' 
WHEN 1 THEN 'ReadUncommitted' 
WHEN 2 THEN 'ReadCommitted' 
WHEN 3 THEN 'Repeatable' 
WHEN 4 THEN 'Serializable' 
WHEN 5 THEN 'Snapshot' END AS TRANSACTION_ISOLATION_LEVEL 
FROM sys.dm_exec_sessions 
where session_id = @@SPID

However, in one of my services, I'm attempting to overwrite the isolation level back to READ_COMMITTED, but it is not taking effect.但是,在我的一项服务中,我试图将隔离级别覆盖回 READ_COMMITTED,但它没有生效。

Given the following:鉴于以下情况:

Selections from application.properties来自 application.properties 的选择

spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.transaction-isolation=TRANSACTION_READ_UNCOMMITTED

JpaConfig.java JpaConfig.java

@Configuration
@EnableJpaRepositories("my.project.repository")
@EntityScan(basePackages = "my.project.model")
@EnableTransactionManagement
public class JpaConfig {
    //DataSource configured by Spring from application.properties
}

MyService.java我的服务.java

@Service
public class MyService {

    @Autowired private MyRepository myRepository;

    @Transactional(isolation = Isolation.READ_COMMITTED)
    public void myMethod() {
        //Logic and call to myRepository.save()
    }
}

MyRepository.java我的资料库.java

public interface MyRepository extends JpaRepository<MyClass, Long> {

}

What am I missing?我错过了什么? I do not have a custom TransactionManager, as I'm allowing @EnableTransactionManagement to configure that for me, as I've found no indication anywhere that I should be providing my own custom implementation so far.我没有自定义 TransactionManager,因为我允许@EnableTransactionManagement为我配置它,因为到目前为止我没有发现任何迹象表明我应该提供我自己的自定义实现。

I have verified that the transaction rollback is properly occurring if an exception is thrown, but I can't figure out why the @Transactional annotation isn't overwriting the isolation level like I'd expect.我已经验证如果抛出异常,事务回滚会正确发生,但我无法弄清楚为什么@Transactional注释没有像我期望的那样覆盖隔离级别。

For what it's worth, the root problem we're trying to solve are the lock contentions on our SQL Server database.就其价值而言,我们试图解决的根本问题是 SQL 服务器数据库上的锁争用。 From what I understand, in SQL Server, even SELECTs put a lock on a table (or row?).据我了解,在 SQL 服务器中,甚至 SELECT 也会锁定表(或行?)。 The DBAs' first suggestion was to add the WITH (NOLOCK) hint to my queries. DBA 的第一个建议是在我的查询中添加 WITH (NOLOCK) 提示。 I can't figure out for the life of me how to cleanly do this without scrapping the use of JPA entirely and using native queries.我一辈子都想不出如何在不完全放弃使用 JPA 并使用本机查询的情况下干净利落地做到这一点。 So, their solution was to use READ_UNCOMMITTED by default, setting READ_COMMITTED explicitly on our write transactions.因此,他们的解决方案是默认使用 READ_UNCOMMITTED,在我们的写入事务上显式设置 READ_COMMITTED。

from the source code of hikari  

    final int level = Integer.parseInt(transactionIsolationName);
            switch (level) {
               case Connection.TRANSACTION_READ_UNCOMMITTED:
               case Connection.TRANSACTION_READ_COMMITTED:
               case Connection.TRANSACTION_REPEATABLE_READ:
               case Connection.TRANSACTION_SERIALIZABLE:
               case Connection.TRANSACTION_NONE:
               case SQL_SERVER_SNAPSHOT_ISOLATION_LEVEL: // a specific isolation level for SQL server only
                  return level;
               default:
                  throw new IllegalArgumentException();
             }

As you see above you have to give numeric value of transaction level like 

spring.datasource.hikari.transaction-isolation=1 

All level numeric values listed:

TRANSACTION_NONE             = 0;
TRANSACTION_READ_UNCOMMITTED = 1;
TRANSACTION_READ_COMMITTED   = 2;
TRANSACTION_REPEATABLE_READ  = 4;
TRANSACTION_SERIALIZABLE     = 8;
SQL_SERVER_SNAPSHOT_ISOLATION_LEVEl =4096;

transactionIsolation事务隔离

This property controls the default transaction isolation level of connections returned from the pool.此属性控制从池返回的连接的默认事务隔离级别。 If this property is not specified, the default transaction isolation level defined by the JDBC driver is used.如果未指定此属性,则使用 JDBC 驱动程序定义的默认事务隔离级别。 Only use this property if you have specific isolation requirements that are common for all queries.仅当您有所有查询通用的特定隔离要求时才使用此属性。 The value of this property is the constant name from the Connection class such as TRANSACTION_READ_COMMITTED, TRANSACTION_REPEATABLE_READ, etc. Default: driver default此属性的值是来自 Connection 类的常量名称,例如 TRANSACTION_READ_COMMITTED、TRANSACTION_REPEATABLE_READ 等。 默认值:驱动程序默认值

ref: https://github.com/brettwooldridge/HikariCP参考: https : //github.com/brettwooldridge/HikariCP

确保您在application.properties 中设置

spring.jpa.hibernate.connection.provider_class=org.hibernate.hikaricp.internal.HikariCPConnectionProvider

Thanks for the detail you provided in your question.感谢您在问题中提供的详细信息。 It really helped in clarifying my scenario and you already provided the answer to use the NOLOCK option.它确实有助于阐明我的场景,并且您已经提供了使用NOLOCK选项的答案。

I was able to figure out how to apply the option using a custom dialect and some query adjustments to force the dialect logic to always be used.我能够弄清楚如何使用自定义方言和一些查询调整来应用该选项以强制始终使用方言逻辑。

We are querying from a sql server database that is a read-only replica of our production database.我们正在从 sql 服务器数据库进行查询,该数据库是我们生产数据库的只读副本。

In our case certain tables used for looking up user characteristics are totally deleted and recreated.在我们的例子中,某些用于查找用户特征的表被完全删除并重新创建。 This ripples into a large amount of locking on the sql server replica during the replication process.在复制过程中,这会波及到 sql 服务器副本上的大量锁定。

We are seeing outliers with worst case query times into the minutes (should be < 10 millisecond).我们看到异常值的最坏情况查询时间达到几分钟(应该小于 10 毫秒)。 We think this is most likely locking related.我们认为这很可能与锁定有关。

I was able to get the WITH (NOLOCK) to be emitted properly with the following approach:我能够通过以下方法正确发出WITH (NOLOCK)

  1. Create a custom Dialect.创建自定义方言。
public class ReadOnlySqlServerDialect extends SQLServer2012Dialect {
    @Override
    public String appendLockHint(LockOptions lockOptions, String tableName) {       
        // in our case the entire db is a replica and we never do any writes       
        return tableName + " WITH (NOLOCK)";
    }
}
  1. Configure hibernate.dialect to point at ReadOnlySqlServerDialect.class.getName()配置 hibernate.dialect 指向ReadOnlySqlServerDialect.class.getName()

  2. Force Queries to use LockModeType.PESSIMISTIC_READ as this bypasses a shield within hibernate and assures that the ReadOnlySqlServerDialect.appendLockHint() method is always called.强制查询使用LockModeType.PESSIMISTIC_READ ,因为这会绕过 hibernate 内的屏蔽并确保始终调用ReadOnlySqlServerDialect.appendLockHint()方法。

return entityMgr.createNamedQuery(UserLog.FIND_BY_EMAIL, UserLog.class)
                .setParameter("email", email)
                .setLockMode(LockModeType.PESSIMISTIC_READ)
                .getSingleResult();
  1. Resulting in SQL generated like this:导致 SQL 生成如下:
select userlog0_.EMAIL, userlog0_.NAME as email0_18_ from APP.USER_LOG userlog0_ WITH (NOLOCK) 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM