我有一个包含多个线程的调度程序,用于更新表中的行。我正在尝试使用“for update skip locked”,但出于某种原因,每个线程都选择并更新同一行,而不是查找下一个无人认领的行。 我有一个包含“id”和“counter”列的“counter”表。
CounterRepository.java
@Repository
public class CounterRepository {
private final JdbcTemplate jdbc;
public CounterRepository(JdbcTemplate jdbc) {this.jdbc = jdbc;}
@Transactional
void update() {
Integer id = jdbc.queryForObject("SELECT id FROM counter ORDER BY counter LIMIT 1 FOR UPDATE SKIP LOCKED;", Integer.class);
Integer counter = jdbc.queryForObject("SELECT counter FROM counter WHERE id = ?", Integer.class, id);
jdbc.update("UPDATE counter set counter = ? WHERE id = ?", 5 - counter, id);
}
}
DbAsyncUpdateApplication.java
@EnableAsync
@EnableScheduling
@SpringBootApplication
public class DbAsyncUpdateApplication {
@Bean(name = "threadPoolTaskExecutor")
public ScheduledExecutorService taskScheduler() throws InterruptedException {
return Executors.newScheduledThreadPool(5);
}
public static void main(String[] args) {
SpringApplication.run(DbAsyncUpdateApplication.class, args);
}
}
调度器.java
@Service
public class Scheduler {
private final CounterRepository repository;
public Scheduler(CounterRepository repository) {this.repository = repository;}
@Scheduled(fixedRate = 1000)
public void start() {
repository.update();
}
}
最佳答案
您编写的 SQL 应该可以在默认隔离级别下正常工作。 问题可能与初始数据有关,也可能与用于更新“计数器”的逻辑有关。
查看围绕“计数器”更新的逻辑,您将每次都将计数器
设置为5 - 计数器
。这意味着该值将在一组固定的两个值之间不断切换。例如。如果对于给定的 id,初始 counter
为 0,程序将继续在 5 和 0 之间切换值。我希望这是预期的。如果是这样,一种可能性(给定本例中的数据)是所有其他行的 counter
值 > 5 并且 JVM 一次只运行一个线程。这样,正在更新的行将始终具有最低的计数器,并且在该行被锁定时没有其他线程会尝试选择另一行。这似乎很少见,但理论上是可能的。
我建议 -
- 每次线程进入和离开
update()
方法时记录一条消息,以检查是否确实有多个线程同时进入该方法 - 检查表中的初始数据并确保计数器更新逻辑在给定此数据的情况下可以正常工作(也可以随意在此处发布数据)
关于java - 带有 Spring JDBC 的 Postgres "SKIP LOCKED",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45569553/