Какова наилучшая практика для обеспечения чтения атомами таблицы базы данных с использованием JDBC?

У меня есть приложение Java, которое читает из таблицы базы данных для выполнения заданий для обработки, и у меня может быть несколько экземпляров этого приложения, работающих на разных серверах, поскольку каждое задание является независимым. После того, как задание будет загружено для обработки, его статус будет обновлен до "running". Я хочу убедиться, что поиск обрабатываемых заданий из каждого экземпляра будет атомарным, как я могу добиться этого с помощью JDBC?

Ответы

Ответ 1

Один подход, который был бы полностью общим * хотя, возможно, немного неэффективным, заключался бы в использовании идентификатора сервера для "требования" к заданию, сначала обновив его статус до этого идентификатора, а затем извлекая задание, основанное на этом значении. Например, если вы работаете с серверами Windows в одной и той же сети, то их имя сервера однозначно идентифицирует их. Если ваш стол выглядел как

JobID  JobName  Status
-----  -------  ---------
    1  Job_A    Completed
    2  Job_B
    3  Job_C

где невостребованные задания имеют статус NULL, тогда ваше приложение, работающее на сервере SERVER1, может потребовать задания, выполнив setAutoCommit(true), а затем

UPDATE Jobs SET Status='SERVER1'
WHERE JobID IN (
    SELECT TOP 1 JobID FROM Jobs 
    WHERE Status IS NULL
    ORDER BY JobID)

Если ExecuteUpdate возвращает 0, отложенные задания не выполняются. Если он возвращает 1, вы можете получить строку с

SELECT JobID, ... FROM Jobs WHERE Status='SERVER1'

а затем обновите свой статус до "Запуск" параметризованным запросом, например

UPDATE Jobs SET Status='Running' WHERE JobID=?

где вы задаете JobID, который вы извлекли из предыдущего SELECT.

* (т.е. не полагаясь на какие-либо конкретные расширения SQL, явную блокировку или обработку транзакций)

Ответ 2

Блокировать таблицу, используя любой механизм, поддерживаемый сервером базы данных.

Например, в Postgres это будет:

LOCK yourtable;

И это ваша таблица на время транзакции.

Другие базы данных будут иметь что-то подобное.

Ответ 3

Используйте ResultSet, у которого есть CONCUR_READ_ONLY и TYPE_FORWARD_ONLY. Если ваш драйвер jdbc базы данных поддерживает его, он будет возвращать только атомное чтение вашего времени выбора.

В соответствии с этой документацией, (Сводная таблица видимости внутренних и внешних изменений)

Только для прямого просмотра отображаются только результаты чтения. CONCUR_READ_ONLY предотвратит ваши внутренние обновления.

Ответ 4

При использовании баз данных транзакционного характера одной популярной практикой является выполнение ROCK-LEVEL LOCKING. Блокировки уровня строк предотвращают изменение нескольких транзакций одной и той же строки. SELECT для UPDATE - это простой способ добиться этого эффекта. Предполагая, что у вас есть таблица процессов:

SELECT process_id, status 
from processes
for UPDATE of status SKIP LOCKED;

Когда закончите обработку, выполните

update processes set status = 'updated'
where process_id = :process_id;             --from before

Проблема

commit;

чтобы освободить блокировку.

Здесь фактический пример

Отказ от ответственности. SELECT FOR UPDATE - это форма пессимистической блокировки и имеет оговорки, как объясняет Burleson. Тем не менее, это может быть жизнеспособным решением, если клиент не является веб-сайтом и чрезвычайно параллелен.

Ответ 5

Проблема

Снять рабочие места, готовые к обработке, и сделать их статус running атомарно.

Решение

Нет необходимости в дополнительных замках. Поскольку операция update уже сама по себе сама по себе с точки зрения одного и того же запроса (см. Выдержку из документов ниже), обновите таблицу jobs, установив статус running тем, которые готовы к обработке и получить результат этого обновления - это будут задания, которые вы сделали для обработки.

Примеры:

Postgres

UPDATE jobs SET status = 'running'
  WHERE status is NULL
RETURNING id;

В терминах JDBC вы можете пойти примерно так:

String sql = "update ... returning ...";
boolean hasResult = statement.execute(sql);
if (hasResult) {
    ResultSet rs = statement.getResult();
}

SQL Server

UPDATE jobs SET status = 'running'
  WHERE status is NULL
OUTPUT UPDATED.id;

Выдержка из документации Postgres которая показывает, как работают 2 транзакции при выполнении UPDATE в той же таблице с тем же запросом:

UPDATE найдет только целевые строки, которые были зафиксированы в начале команды время. Однако такая целевая строка может быть уже обновлена ​​(или удалена или заблокирована) другой параллельной транзакцией к моменту ее найденный. В этом случае потенциальный обновитель будет ждать для первого обновление транзакции для фиксации или откат (если оно все еще находится в прогресс).

Ответ 6

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

Таблица будет выглядеть так:

JobID  JobName  Server  Status
-----  -------  ------- ---------
    1  Job_A    host-1  Completed
    2  Job_A    host-2  Working
    3  Job_B    host-3  Working

Если у вас несколько экземпляров на одном и том же хосте, добавьте также идентификатор процесса:

JobID  JobName  Server  ProcessID  Status
-----  -------  ------- ---------- ---------
    1  Job_A    host-1  1000       Completed
    2  Job_A    host-2  1000       Working
    3  Job_A    host-2  1001       Working
    5  Job_B    host-3  1000       Working