我们有多个Spring Batch作业,每个作业都使用CommandLineJobRunner在自己的java实例中运行。所有作业都是同时启动的,只读取/写入平面文件,并更新SQL Server中托管的相同Spring Batch元数据。唯一涉及的数据库是SpringBatch元数据数据库。
当多个作业同时启动时,我们会得到SQL死锁异常。下面可以找到更详细的堆栈跟踪。从数据库的角度来看,死锁受害者正在执行以下操作之一:Insert into BATCH_JOB_SEQ default values or Delete From BATCH_JOB_SEQ where ID<some_number。
我们使用默认的MapJobRegistry,以及默认的作业存储库或指定JobRepositoryFactoryBean。对于用于与Spring Batch数据库交互的数据源,我们尝试了DriverManagerDataSource或DBCP2池BasicDataSource,两者都使用标准的Microsoft SQL Server SQLServerDriver。我可以上传更具体的配置文件,但在测试中,只要我使用SQL Server和标准的Spring配置,就会出现问题。
在我的调查中,我认为问题是由于默认的增量器类org.springframework.jdbc.support.incrementer.SqlServerMaxValueIncrementer如何将作业和步骤实例ID与SQL Server数据库表的构建方式结合起来进行增量。SqlServerMaxValueIncrementer中的代码是同步的,所以如果我们在同一个Java实例中运行所有作业,这不会是一个问题。
如果我们在DB2数据库中实现SpringBatch元数据,我们就不会有问题。SQL Server实现使用实际的表,DB2实现使用序列对象。
有人碰到这个问题了吗?我只是错过了什么吗?似乎每当我们遇到这样的问题时,它就像在yyy中设置xxx一样简单。如果没有,有人知道为什么Spring Batch没有在SQL Server实现中实现序列对象吗?
堆栈跟踪:
[org.springframework.batch.core.launch.support.CommandLineJobRunner] - <Job Terminated in error: Could not increment identity; nested exception is com.microsoft.sqlserver.jdbc.SQLServerException: Transaction (Process ID 74) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.>
org.springframework.dao.DataAccessResourceFailureException: Could not increment identity;
nested exception is com.microsoft.sqlserver.jdbc.SQLServerException:
Transaction (Process ID 74) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
at org.springframework.jdbc.support.incrementer.SqlServerMaxValueIncrementer.getNextKey(SqlServerMaxValueIncrementer.java:124)
at org.springframework.jdbc.support.incrementer.AbstractDataFieldMaxValueIncrementer.nextLongValue(AbstractDataFieldMaxValueIncrementer.java:1
28)
at org.springframework.batch.core.repository.dao.JdbcJobInstanceDao.createJobInstance(JdbcJobInstanceDao.java:108)
at org.springframework.batch.core.repository.support.SimpleJobRepository.createJobExecution(SimpleJobRepository.java:135)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
配置:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:batch="http://www.springframework.org/schema/batch"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="
http://www.springframework.org/schema/batch
http://www.springframework.org/schema/batch/spring-batch.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
lazy-init="true">
<property name="dataSource" ref="batchPoolingDataSource" />
</bean>
<bean id="jobRegistry"
class="org.springframework.batch.core.configuration.support.MapJobRegistry" />
<bean id="jobRegistryBeanPostProcessor"
class="org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor">
<property name="jobRegistry" ref="jobRegistry" />
</bean>
<bean id="jobRepository"
class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean">
<property name="databaseType" value="SQLSERVER" />
<property name="dataSource" ref="batchPoolingDataSource" />
<property name="transactionManager" ref="transactionManager" />
</bean>
<bean id="jobLauncher"
class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
</bean>
<bean id="jobExplorer"
class="org.springframework.batch.core.explore.support.JobExplorerFactoryBean">
<property name="dataSource" ref="batchPoolingDataSource" />
</bean>
<bean id="jobOperator"
class="org.springframework.batch.core.launch.support.SimpleJobOperator">
<property name="jobExplorer" ref="jobExplorer" />
<property name="jobLauncher" ref="jobLauncher" />
<property name="jobRegistry" ref="jobRegistry" />
<property name="jobRepository" ref="jobRepository" />
</bean>
<bean class="org.springframework.batch.core.scope.StepScope">
<property name="proxyTargetClass" value="true" />
</bean>
<bean id="batchPoolingDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
<property name="url" value="jdbc:sqlserver://server info" />
<property name="username" value="${batch.jdbc.user}" />
<property name="password" value="${batch.jdbc.password}" />
<property name="initialSize" value="5" />
<property name="maxTotal" value="15" />
<property name="maxWaitMillis" value="5000" />
</bean>
<bean id="batchDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" >
<property name="driverClassName" value="org.springframework.jdbc.datasource.DriverManagerDataSource" />
<property name="url" value="jdbc:sqlserver://server info" />
<property name="username" value="${batch.jdbc.user}" />
<property name="password" value="${batch.jdbc.password}" />
</bean>
在进一步研究了这一点,并部分着手开发支持JobRepository
并使用SQL Server IDENTITY
而不是序列的DAO版本后,我偶然发现了在不进行任何配置的情况下解决这一问题的方法。
解决此问题的简单方法是配置JobRepository
的databaseType
和isolationLevelForCreate
属性。以下是我在SQLServer2008:中使用的设置
<bean id="jobRepository"
class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="transactionManager" />
<property name="databaseType" value="SQLSERVER" />
<property name="isolationLevelForCreate" value="ISOLATION_REPEATABLE_READ" />
</bean>
我已经用一组Quartz工作启动的30个工作(相同的工作具有不同的参数)测试了这一点,到目前为止,我还没有看到任何问题。
在启动作业时,我还保留了重试代码(请参阅问题注释),只是为了捕捉任何可能的死锁并允许其重试。这可能是一个没有意义的问题,但我不能冒险让工作无法启动。
我认为,在Spring Batch文档中提到这些关于在使用SQL Server作为数据源时在给定时间启动多个作业的设置,对其他人会很有帮助。再说一遍,我想没有多少人会被SQL Server所困扰。