为什么我在 JPA 中使用 Oracle 的悲观锁定不起作用

Why my pessimistic Locking in JPA with Oracle is not working

我正在尝试为 运行 在不同 JBoss 节点中的 cron 作业实现某种信号量。我正在尝试使用数据库 (Oracle 11g) 作为锁定机制,使用一个 table 来同步不同节点中的 cron 作业。 table很简单:

CREATE TABLE SYNCHRONIZED_CRON_JOB_TASK
(
   ID            NUMBER(10)           NOT NULL,
   CRONJOBTYPE   VARCHAR2(255 Byte),
   CREATIONDATE  TIMESTAMP(6)         NOT NULL,
   RUNNING       NUMBER(1)
);

ALTER TABLE SYNCHRONIZED_CRON_JOB_TASK
   ADD CONSTRAINT PK_SYNCHRONIZED_CRON_JOB_TASK
   PRIMARY KEY (ID); 

因此,当作业开始时,它会在 table 中搜索其 cronjobtype 的条目,并检查它是否已经 运行ning。如果不是,它将条目设置 运行ning 标志更新为 true。第一个 select 是使用 Hibernate 和 Pessimistic Lock 使用 JPA CriteriaApi 制作的。

query.setLockMode(javax.persistence.LockModeType.PESSIMISTIC_WRITE);

所有这些操作都是在一次交易中完成的。

当一个进程 运行s 时,它进行的查询如下:

[Server:server-two] 10:38:00,049 INFO  [stdout] (scheduler-2) 2015-04-30 10:38:00,048 WARN  (Loader.java:264) - HHH000444: Encountered request for locking however dialect reports that database prefers locking be done in a separate select (follow-on locking); results will be locked after initial query executes
[Server:server-two] 10:38:00,049 INFO  [stdout] (scheduler-2) Hibernate: select * from ( select distinct synchroniz0_.id as id1_127_, synchroniz0_.creationDate as creation2_127_, synchroniz0_.running as running3_127_, synchroniz0_.CRONJOBTYPE as CRONJOBT4_127_ from SYNCHRONIZED_CRON_JOB_TASK synchroniz0_ where synchroniz0_.CRONJOBTYPE=? ) where rownum <= ?
[Server:server-two] 10:38:00,053 INFO  [stdout] (scheduler-2) Hibernate: select id from SYNCHRONIZED_CRON_JOB_TASK where id =? for update
[Server:server-two] 10:38:00,056 INFO  [stdout] (scheduler-2) Hibernate: update SYNCHRONIZED_CRON_JOB_TASK set creationDate=?, running=?, CRONJOBTYPE=? where id=?

这个警告没有问题,你可以看到第一个select然后是一个select更新,所以Oracle应该阻止其他select在这一行上的操作。 但这就是重点,查询没有被阻止,因此两个作业可以进入并进行 select 和更新而不会出现问题。锁不起作用,如果我们同时 运行 两个 cron 作业就可以看到它:

[Server:server-one] 10:38:00,008 INFO  [stdout] (scheduler-3) 2015-04-30 10:38:00,008 WARN  (Loader.java:264) - HHH000444: Encountered request for locking however dialect reports that database prefers locking be done in a separate select (follow-on locking); results will be locked after initial query executes
[Server:server-two] 10:38:00,008 INFO  [stdout] (scheduler-2) 2015-04-30 10:38:00,008 WARN  (Loader.java:264) - HHH000444: Encountered request for locking however dialect reports that database prefers locking be done in a separate select (follow-on locking); results will be locked after initial query executes
[Server:server-two] 10:38:00,009 INFO  [stdout] (scheduler-2) Hibernate: select * from ( select distinct synchroniz0_.id as id1_127_, synchroniz0_.creationDate as creation2_127_, synchroniz0_.running as running3_127_, synchroniz0_.CRONJOBTYPE as CRONJOBT4_127_ from SYNCHRONIZED_CRON_JOB_TASK synchroniz0_ where synchroniz0_.CRONJOBTYPE=? ) where rownum <= ?
[Server:server-one] 10:38:00,009 INFO  [stdout] (scheduler-3) Hibernate: select * from ( select distinct synchroniz0_.id as id1_127_, synchroniz0_.creationDate as creation2_127_, synchroniz0_.running as running3_127_, synchroniz0_.CRONJOBTYPE as CRONJOBT4_127_ from SYNCHRONIZED_CRON_JOB_TASK synchroniz0_ where synchroniz0_.CRONJOBTYPE=? ) where rownum <= ?
[Server:server-two] 10:38:00,013 INFO  [stdout] (scheduler-2) Hibernate: select id from SYNCHRONIZED_CRON_JOB_TASK where id =? for update
[Server:server-one] 10:38:00,014 INFO  [stdout] (scheduler-3) Hibernate: select id from SYNCHRONIZED_CRON_JOB_TASK where id =? for update
[Server:server-two] 10:38:00,016 INFO  [stdout] (scheduler-2) 2015-04-30 10:38:00,015 DEBUG (SynchronizedCronJobService.java:65) - Task read SynchronizedCronJobTask [id=185, type=AlertMailTaskExecutor, creationDate=2015-04-25 07:11:33.0, running=false]
[Server:server-two] 10:38:00,018 INFO  [stdout] (scheduler-2) Hibernate: update SYNCHRONIZED_CRON_JOB_TASK set creationDate=?, running=?, CRONJOBTYPE=? where id=?
[Server:server-one] 10:38:00,022 INFO  [stdout] (scheduler-3) 2015-04-30 10:38:00,022 DEBUG (SynchronizedCronJobService.java:65) - Task read SynchronizedCronJobTask [id=185, type=AlertMailTaskExecutor, creationDate=2015-04-25 07:11:33.0, running=false]
[Server:server-one] 10:38:00,024 INFO  [stdout] (scheduler-3) Hibernate: update SYNCHRONIZED_CRON_JOB_TASK set creationDate=?, running=?, CRONJOBTYPE=? where id=?

我已经尝试将此 select 用于 SQL 工具 (SQLWorkbenchJ) 上的更新,该工具具有两个连接并且阻塞在该工具中运行良好。但是,如果我将此 select 用于 SQL 工具的更新并启动 cron 作业,它们不会被阻塞并且 运行 没有问题。

我认为问题来自 JPA、Hibernate 或 Oracle 驱动程序,但我不确定。知道问题出在哪里吗?我应该使用另一个策略吗? 提前致谢。

将锁定模式设置为PESSIMISTIC_READ,因为您需要第二台服务器在更改数据之前知道第一台服务器的更改。

最后我设法使它工作,但进行了一些修改。这个想法是使用 LockModeType.PESSIMISTIC_FORCE_INCREMENT 而不是 PESSIMISTIC_WRITE。使用此锁定模式,Cron 作业的行为如下:

  1. 当第一个作业进行 select 更新时,一切都按预期进行,但对象上的版本发生了变化。
  2. 如果另一个作业试图进行相同的 select 而第一个作业仍在其事务中,JPA 将启动一个 OptimisticLockException,因此如果您捕获到该异常,您可以确定它是为读锁而抛出的。

此解决方案有多种对应方案:

  1. SynchronizedCronJobTask 必须有一个版本字段,并且受@Version 的版本控制
  2. 您需要处理 OptimisticLockException,并且应该在事务服务方法之外捕获它,以便在发生解除锁定时进行回滚。
  3. 恕我直言,这是一个不优雅的解决方案,比 Cron 作业等待先前作业完成的简单锁定更糟糕。

我可以证实里卡多的观察。我用 H2 数据库测试了几种锁定模式,并且都按预期工作。与 Oracle 数据库结合使用时,悲观锁模式均无法正常工作。乐观锁我没试过,但是有一个lockmode完全不能和top dog一起使用,真是太神奇了