多个 Spring Batch 作业同时执行导致死锁

ptj*_*ack 5 java sql-server spring spring-batch spring-boot

我正在使用 spring Batch 4.0.0、Spring Boot 2.2.0、java jdk 12.0.2、db sql server 2016。

多个 Spring Batch 作业同时执行会导致 Spring Batch 元数据表中出现死锁。

我尝试了不同的解决方案:

  1. 我在元数据表上设置身份
ALTER TABLE [OWN].[BATCH_JOB_EXECUTION_SEQ]
ADD CONSTRAINT [PK_BATCH_JOB_EXECUTION_SEQ] PRIMARY KEY CLUSTERED ([ID] ASC);

ALTER TABLE [OWN].[BATCH_JOB_SEQ]
ADD CONSTRAINT [PK_BATCH_JOB_SEQ] PRIMARY KEY CLUSTERED ([ID] ASC);

ALTER TABLE [OWN].[BATCH_STEP_EXECUTION_SEQ]
ADD CONSTRAINT [PK_BATCH_STEP_EXECUTION_SEQ] PRIMARY KEY CLUSTERED ([ID] ASC);
Run Code Online (Sandbox Code Playgroud)
  1. 我重写了 JobRepository 和 JobExplorer 来配置隔离级别
    @Override
    protected JobRepository createJobRepository() throws Exception {
        JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
        factory.setDatabaseType(DATABASE_TYPE);
        factory.setIsolationLevelForCreate(ISOLATION_REPEATABLE_READ);
        factory.setDataSource(dataSource);
        factory.setTransactionManager(getTransactionManager());
        factory.setTablePrefix(TABLE_PREFIX);
        factory.afterPropertiesSet();
        return factory.getObject();
    }

    @Override
    protected JobExplorer createJobExplorer() throws Exception {
        JobExplorerFactoryBean jobExplorerFactoryBean = new JobExplorerFactoryBean();
        jobExplorerFactoryBean.setDataSource(dataSource);
        jobExplorerFactoryBean.afterPropertiesSet();
        jobExplorerFactoryBean.setTablePrefix(TABLE_PREFIX);
        return jobExplorerFactoryBean.getObject();
    }
Run Code Online (Sandbox Code Playgroud)

但是当我开始多项工作时,我经常/总是遇到相同的错误:

无法增加身份;嵌套异常是 com.microsoft.sqlserver.jdbc.SQLServerException:事务在锁资源上与另一个进程发生死锁,并已被选为死锁受害者。重新运行事务。

ptj*_*ack 5

我已经找到解决方案并发布给有同样问题的人;我从未找到完整且全面的解决方案,因此我想解释如何解决它以及为什么会发生,让我们开始吧。这个死锁是由 BATCH_STEP_EXECUTION_SEQ、BATCH_JOB_EXECUTION_SEQ 和 BATCH_JOB_SEQ 表的更新和删除引起的,但为什么会发生呢?也许是因为事情没有得到适当的管理,但真正的问题是sql server开始使用sql server 2012中的序列,所以spring批处理必须使用这个表来管理序列,对于那些使用旧版本的人来说,我没有解决方案sql server 版本高于 2012 年,本指南仅适用于 2012 年以后的版本

1. 在数据库中创建序列。

请注意从尚未处理的 ID 开始序列,否则如果从 1 开始,则必须清理 Spring 表以及可能使用作业 ID 的表。

DROP TABLE [OWN].[BATCH_JOB_SEQ]
GO


DROP TABLE [OWN].[BATCH_JOB_EXECUTION_SEQ]
GO


DROP TABLE [OWN].[BATCH_STEP_EXECUTION_SEQ]
GO


CREATE SEQUENCE [OWN].[BATCH_JOB_SEQ] 
 AS [bigint]
 START WITH 1000
 INCREMENT BY 1
 MINVALUE -9223372036854775808
 MAXVALUE 9223372036854775807
 CACHE 
GO


CREATE SEQUENCE [OWN].[BATCH_JOB_EXECUTION_SEQ] 
 AS [bigint]
 START WITH 1000
 INCREMENT BY 1
 MINVALUE -9223372036854775808
 MAXVALUE 9223372036854775807
 CACHE 
GO

CREATE SEQUENCE [OWN].[BATCH_STEP_EXECUTION_SEQ] 
 AS [bigint]
 START WITH 1000
 INCREMENT BY 1
 MINVALUE -9223372036854775808
 MAXVALUE 9223372036854775807
 CACHE 
GO
Run Code Online (Sandbox Code Playgroud)

2. 也重写 JobRepository 方法和 JobExplorer (我个人必须添加它,否则我会遇到有关 spring 批处理表前缀的问题)。

将隔离级别设置为 REPEATABLE_READ,否则您将在数据库上出现新的并发错误(就个人而言,如果没有此设置,我在 BATCH_JOB_EXECUTION 或 BATCH_JOB_INSTANCE 上遇到问题,我现在不记得了)。

@Component
public class SQLServerConfig extends DefaultBatchConfigurer{

  @Autowired
  private DataSource dataSource;

  @Value("${spring.batch.tablePrefix}")
  private String tablePrefix;

  @Override
  protected JobRepository createJobRepository() throws Exception {
      JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
      factory.setDatabaseType(DatabaseType.SQLSERVER.name());
      factory.setDataSource(dataSource);
      factory.setTransactionManager(getTransactionManager());
      factory.setTablePrefix(tablePrefix);
      factory.setIncrementerFactory(new CustomDataFieldMaxValueIncrementerFactory(dataSource));
      factory.setIsolationLevelForCreate("ISOLATION_REPEATABLE_READ");
      factory.afterPropertiesSet();
      return factory.getObject();
  }

  @Override
  protected JobExplorer createJobExplorer() throws Exception {
      JobExplorerFactoryBean jobExplorerFactoryBean = new JobExplorerFactoryBean();
      jobExplorerFactoryBean.setDataSource(dataSource);
      jobExplorerFactoryBean.afterPropertiesSet();
      jobExplorerFactoryBean.setTablePrefix(tablePrefix);
      return jobExplorerFactoryBean.getObject();
  }

}
Run Code Online (Sandbox Code Playgroud)

3. 设置 CustomDataFieldIncrementerFactory,因为现在它开始管理它的序列。

 public class CustomDataFieldMaxValueIncrementerFactory extends DefaultDataFieldMaxValueIncrementerFactory {


  private DataSource dataSource;


  public CustomDataFieldMaxValueIncrementerFactory(DataSource dataSource) {
      super(dataSource);
      this.dataSource = dataSource;
  }


  @Override
  public DataFieldMaxValueIncrementer getIncrementer(String incrementerType, String incrementerName) {
      DatabaseType databaseType = DatabaseType.valueOf(incrementerType.toUpperCase());
      DataFieldMaxValueIncrementer dataFieldMaxValueIncrementer = null;
      if (databaseType == DatabaseType.SQLSERVER) {
          dataFieldMaxValueIncrementer =  new CustomSqlServerMaxValueIncrementer(dataSource, incrementerName);
      } else {
          dataFieldMaxValueIncrementer = super.getIncrementer(incrementerType, incrementerName);
      }
      return dataFieldMaxValueIncrementer;
  }
}
Run Code Online (Sandbox Code Playgroud)

就这样,现在一切正常了!!!

我希望它对某人有用。