首页 > 解决方案 > 在应用程序代码(如@Transactional)中设置隔离级别与在数据库服务器上配置的隔离级别之间有什么关系?

问题描述

我很难将所有信息拼凑在一起。所以让我尽可能详细地解释一下。

在 spring-jdbc、spring-transaction、MySQL db 应用程序上工作,我希望能够在选择更新时锁定表中的数据行。原因是,我有在不同阶段转换数据的业务逻辑。因此,如果应用程序实例(在 server-1 上)拾取记录进行处理,那么它不应该被另一个实例拾取。这有点类似于这个问题。但是由于与 OP 对该问题的相同原因,我不能接受这个答案。

所以在仔细查看了spring-transaction 参考文档并了解了如何使用 jdbcTemplate 配置事务管理之后,我的 spring 应用程序设置如下(仅相关部分):

应用程序上下文.xml

...
...
<context:annotation-config />
...
<context:component-scan base-package="org.foo.bar"/>
...
<!-- Enable Annotation based Declarative Transaction Management -->
<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager" />
<bean id="transactionManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>
...
<bean id="dataSource"
      class="org.apache.commons.dbcp2.BasicDataSource"  destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://dbserver:3306" />
    <property name="username" value="foo" />
    <property name="password" value="baz" />
    <property name="initialSize" value="10" />
    <property name="maxTotal" value="20"/>
</bean>

具有目标方法的服务类:TransactionSvcImpl.java

public class TransactionSvcImpl implements TransactionSvc {
    @Autowired
    DatabaseAccessService dbAccessService;

    @Transactional(isolation = Isolation.SERIALIZABLE, propagation = Propagation.REQUIRES_NEW)
    @Override
    public List<Foo> getFooForUpdate() {
        // Below call executes a SQL like - "SELECT * FROM some_t WHERE id = 1 FOR UPDATE"
        List<foo> foos = dbAccessService.getSomeRecord();
        dbAccessService.updateTheRecord(foos, Status.PROCESSING);
        return foos;
    }
}

现在,我按照这个答案中的描述测试了隔离级别在 MySQL 中的工作方式。但是,要使“选择更新”工作,我必须更改数据库服务器本身的以下属性:

SET tx_isolation = 'SERIALIZABLE';
SET AUTOCOMMIT=0;

如果不更改这些设置,我就无法开始SELECT ... FOR UPDATE工作。

一旦我手动测试了它,我想看看这在我的应用程序代码运行时是否有任何影响。因此,当我保持一个 mysql 命令行会话处于活动状态时,我启动了我的应用程序并在该dbAccessService.updateTheRecord();行放置了一个断点。然后在另一个会话中,我尝试选择这一行(不select for update,只是一个简单的select)语句。现在我可以看到该行由于我的应用程序代码启动的先前事务而被锁定。

问题:

  1. 要通过 spring-transaction 实现行锁定(SELECT ... FOR UPDATE行为),我是否必须更改数据库服务器上的设置?喜欢isolation_level 和自动提交?
  2. 假设 MySQL 服务器在默认值下运行 (ISOLATION_LEVEL = REPEATABLE-READS, AUTOCOMMIT=1)。现在这仍然有效吗?意思是,如果我像上面那样在目标方法上配置事务,一个应用程序实例正在读取的行是否会保持锁定状态,以便任何其他尝试读取该行的实例都将被阻止?
  3. 我们在注解上添加的配置如何@Transactional使其进入(或影响)数据库?我知道我们可以为不同的范围设置事务特性,如这里 MySQL 文档的表 13.9 中所述。基本上下面是我对这一切如何运作的想象或猜测。这是对的吗?

这一切是如何运作的?

标签: javamysqlspring-jdbcspring-transactionstransaction-isolation

解决方案


产生影响的事情是自动提交。通常自动提交是开启的(每个查询都是它自己的事务),并且要使用您需要使用的事务BEGINSTART TRANSACTION在您的代码中,执行您需要在事务中执行的操作,然后显式调用COMMIT.

1) 服务器上的 transaction_isolation 设置将更改服务器上的默认值。但是您可以在开始事务之前在本地会话中更改它们,而无需更改服务器范围的默认值。

2) 是的,不同会话之间的 tx_isolation 可能不同。

3)不确定我能不能完全回答这个问题,因为它似乎是特定于 Spring 的,我对此并不流利。然而,总的来说,Spring 没有真正的方法来确保或实现数据库级别的事务性和锁定而不显式锁定整个表,因此最终它所能做的就是使用底层数据库功能,并尝试比底层数据库更聪明巨大的性能和并发损失。


推荐阅读