java - 在应用程序代码(如@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
)语句。现在我可以看到该行由于我的应用程序代码启动的先前事务而被锁定。
问题:
- 要通过 spring-transaction 实现行锁定(
SELECT ... FOR UPDATE
行为),我是否必须更改数据库服务器上的设置?喜欢isolation_level 和自动提交? - 假设 MySQL 服务器在默认值下运行 (ISOLATION_LEVEL = REPEATABLE-READS, AUTOCOMMIT=1)。现在这仍然有效吗?意思是,如果我像上面那样在目标方法上配置事务,一个应用程序实例正在读取的行是否会保持锁定状态,以便任何其他尝试读取该行的实例都将被阻止?
- 我们在注解上添加的配置如何
@Transactional
使其进入(或影响)数据库?我知道我们可以为不同的范围设置事务特性,如这里 MySQL 文档的表 13.9 中所述。基本上下面是我对这一切如何运作的想象或猜测。这是对的吗?
这一切是如何运作的?
- 使用 @Transactional 注释的类/方法将导致创建一个代理对象,以便添加所有事务性内容以及用户实现的业务逻辑。
- 获取数据库连接并创建数据库会话。
- 对于本次会话,根据中提供的配置,设置了
@Transactional
合适的隔离级别(不知道autocommit会受到怎样的影响) 事务完成后,会话关闭。创建的任何新会话都将相应地设置隔离特征。
SELECT ... FOR UPDATE
有没有更好的方法让我使用 spring-jdbc、spring-tx实现所需的功能?我可能会引入我自己的锁定机制,比如引入一个新的布尔列selected_for_processing
,而不是选择,而是通过设置selected_for_processing
列来更新要选择的记录。在下一步中,我将根据此标志和另一个状态标准进行选择。所以这样一条记录只会被处理一次。但我想知道是否有常规或配置方式来实现这一点?我不能使用 JPA 或任何 spring-data-x 库。spring-data- library 与 springframework 5.xx 或更高版本一起使用,但我被 springframework 4.2.3 卡住了,因此 spring-jdbc,spring-tx。
解决方案
产生影响的事情是自动提交。通常自动提交是开启的(每个查询都是它自己的事务),并且要使用您需要使用的事务BEGIN
或START TRANSACTION
在您的代码中,执行您需要在事务中执行的操作,然后显式调用COMMIT
.
1) 服务器上的 transaction_isolation 设置将更改服务器上的默认值。但是您可以在开始事务之前在本地会话中更改它们,而无需更改服务器范围的默认值。
2) 是的,不同会话之间的 tx_isolation 可能不同。
3)不确定我能不能完全回答这个问题,因为它似乎是特定于 Spring 的,我对此并不流利。然而,总的来说,Spring 没有真正的方法来确保或实现数据库级别的事务性和锁定而不显式锁定整个表,因此最终它所能做的就是使用底层数据库功能,并尝试比底层数据库更聪明巨大的性能和并发损失。
推荐阅读
- javascript - React Carbon DataTable 未获取所选内容的 onclick 事件(值)
- c++ - 关于cppreference.com对decltype的解释的2个问题
- .net-core - 将其他依赖程序集发布到 azure 应用服务
- r - R中漏斗图中的翻转轴
- java - 用于在 Java 17 中运行时打开整个模块的 JVM 标志
- javascript - 如何将ID添加到最外层使用 plotly JSON 转储(来自 python 应用程序)的 plotly 图表标签?
- java - Android Studio jTDS java.lang.NullPointerException java.sql.Statement java.sql.Connection.createStatement() 在空对象引用上
- python - TemplateDoesNotExist 在 /index.html 后将 settings.py 分成 3 个单独的文件
- python - 在列表中返回错误的真/假值
- django - 使用 X-Accel-Redirect 在 Nginx-Django 中下载文件