Почему моя пессимистическая блокировка в JPA с Oracle не работает

Я пытаюсь реализовать какие-то семафоры для заданий cron, которые работают в разных узлах JBoss. Я пытаюсь использовать базу данных (Oracle 11g) в качестве механизма блокировки, используя одну таблицу для синхронизации заданий cron на разных узлах. Таблица очень проста:

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); 

Поэтому, когда задание запускается, оно ищет в таблице запись своего cronjobtype и проверяет, выполняется ли оно уже. Если это не так, он обновляет флаг запуска настройки входа на true. Этот первый выбор выполняется с помощью JPA CriteriaApi с использованием Hibernate и Pessimistic Lock.

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

Все эти операции выполняются в рамках одной транзакции.

Когда выполняется один процесс, он запрашивает следующее:

[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=?

С этим предупреждением проблем нет, вы можете увидеть сначала выбор, а затем выбор для обновления, поэтому Oracle должен заблокировать другие операции выбора в этой строке. Но в том-то и дело, что запросы не блокируются, поэтому два задания могут войти, сделать выбор и обновить без проблем. Блокировка не работает, мы можем увидеть это, если одновременно запустим два задания 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=?

Я пытался сделать этот выбор для обновления на инструменте SQL (SQLWorkbenchJ) с двумя подключениями, и блокировка работает нормально в этом инструменте. Но если я выберу это для обновления на инструменте SQL и запусту задания cron, они не блокируются и запускаются без проблем.

Я думаю, что проблема связана с JPA, Hibernate или драйвером Oracle, но я не уверен. Любая идея о том, где проблема? Должен ли я использовать другую стратегию? Заранее спасибо.

3 ответа

Решение

Наконец мне удалось заставить его работать, но с некоторыми изменениями. Идея состоит в том, чтобы использовать LockModeType.PESSIMISTIC_FORCE_INCREMENT вместо PESSIMISTIC_WRITE. В этом режиме блокировки задания Cron работают следующим образом:

  1. Когда первое задание делает выбор для обновления, все идет как положено, но версия объекта изменяется.
  2. Если другое задание пытается сделать тот же выбор, в то время как первое еще находится в его транзакции, JPA запускает OptimisticLockException, поэтому, если вы перехватите это исключение, вы можете быть уверены, что оно было сгенерировано для блокировки чтения.

Это решение имеет различные аналоги:

  1. SynchronizedCronJobTask должен иметь поле версии и находиться под контролем версии с помощью @Version
  2. Вам нужно обработать OptimisticLockException, и оно должно быть перехвачено за пределами метода транзакционного сервиса, чтобы выполнить откат, когда происходит де-блокировка.
  3. IMHO - это не элегантное решение, гораздо хуже, чем просто замок, где Cron Jobs ждут окончания предыдущих работ.

Установите режим блокировки на PESSIMISTIC_READ, потому что вам нужно, чтобы второй сервер знал об изменениях первого сервера до изменения данных.

Я могу подтвердить наблюдение Рикардоса. У меня есть несколько Lock-Modes, протестированных с базой данных H2, и все работали, как и ожидалось. Ни один из пессимистичных режимов блокировки не работал правильно в сочетании с базой данных Oracle. Я не пробовал оптимистичную блокировку, но удивительно, что есть режим блокировки, который вообще не работает с верхней собакой.

Другие вопросы по тегам