Какова наилучшая практика для обеспечения чтения атомами таблицы базы данных с использованием 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