ShedLock调度任务报错:主键重复,未更新现有行却插入新行
问题原因分析
这个异常的核心原因是**@Transactional注解与ShedLock的锁操作事务冲突**:
当你的定时方法同时标注了@Transactional和@SchedulerLock时,ShedLock的锁获取逻辑(插入/更新shedlock表)会被包裹在方法的事务中。当多个实例同时触发任务时:
- 第一个实例执行锁插入操作,但因为事务未提交,这条锁记录还没被其他实例可见;
- 其他实例此时尝试插入同主键(
myJob)的锁记录,直接触发主键重复的SQL异常; - 第一个实例的事务提交后,锁记录才会持久化,但其他实例已经因为异常退出,不会再执行任务逻辑——这就是为什么你看到任务实际正常(仅一个实例执行),但其他实例抛错的原因。
解决方案
根据你的业务需求,选择以下任意一种方式解决:
1. 移除方法上的@Transactional(优先推荐,如果业务不需要事务)
如果你的任务逻辑不需要事务支持,直接去掉@Transactional注解,让ShedLock的锁操作在独立的事务中执行,这样多个实例会先竞争锁,只有拿到锁的实例才会执行任务逻辑,不会出现插入冲突。
修改后的代码:
@Scheduled(cron = "0 0 1 * * MON") @SchedulerLock(name = "myJob", lockAtLeastFor = "PT5M") public void myJob() { // 业务代码 }
2. 拆分方法,将锁操作与事务逻辑分离
如果任务逻辑必须使用事务,把带@SchedulerLock的方法和带@Transactional的业务方法拆分:
@Scheduled(cron = "0 0 1 * * MON") @SchedulerLock(name = "myJob", lockAtLeastFor = "PT5M") public void myJobWrapper() { // 锁操作在独立事务中执行,获取锁后再调用带事务的业务方法 myJobTransactional(); } @Transactional public void myJobTransactional() { // 实际业务代码 }
3. 配置ShedLock使用独立事务
如果不想拆分方法,可以给JdbcTemplateLockProvider指定事务管理器,让锁操作在独立事务中执行,不受方法本身的@Transactional影响:
@Bean public LockProvider lockProvider(DataSource dataSource, PlatformTransactionManager transactionManager) { return new JdbcTemplateLockProvider( JdbcTemplateLockProvider.Configuration.builder() .withJdbcTemplate(new JdbcTemplate(dataSource)) .withTableName("shedlock") .usingDbTime() .withTransactionManager(transactionManager) // 指定独立事务管理器 .build() ); }
内容的提问来源于stack exchange,提问作者Tudor Cheregi
相关产品推荐
相关产品推荐

