Eu tenho users
mesa
CREATE TABLE tasks(
id UUID PRIMARY KEY,
status TEXT REFERENCES statuses, -- available statuses are 'NEW', 'PENDING', 'PROCESSING', 'COMPLETE'
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NULL
);
Quero ter certeza de que apenas um processo mudará o status de PENDING
para PROCESSING
no caso de várias tentativas de atualizações simultâneas; o restante deve observar zero linhas atualizadas.
A alteração do status da tarefa é acionada pelo processamento de solicitações HTTP externas. É importante que apenas uma solicitação resulte em sucesso. O restante deve detectar a alteração e retornar 409. Conflito. Não temos controle sobre quantas solicitações simultâneas podemos receber.
A instrução de atualização é a primeira coisa feita como parte de uma transação maior, depois que update
o processo observa que ele atualizou tasks
a linha (a contagem de linhas atualizadas é 1), ele posteriormente insere/atualiza alguns dados em outras tabelas, etc.
Eu testei a seguinte consulta de atualização
UPDATE tasks
SET status = 'PROCESSING'
WHERE id = '00000000-0000-0000-0000-000000000000' AND status = 'PENDING'
usando Java JDBC, múltiplos threads, conexões etc. e observo que apenas um processo faz atualizações ao usar o nível de isolamento padrão READ COMMITTED
.
Quando pensei sobre esse problema, pensei que precisaria FOR UPDATE
de um nível UPDATE
de declaração ou SERIALIZABLE
isolamento na transação, mas em testes reais não consegui detectar inconsistências no caso de múltiplas atualizações.
Minha pergunta: é suficiente filtrar id
e status
garantir que apenas um processo realizará a atualização ou preciso de ações adicionais para excluir comportamentos inesperados em geral?
Sim.
Com o nível de isolamento padrão
READ COMMITTED
, a primeira transação a atualizar a linha a bloqueia para gravação . Até que a transação seja confirmada, as transações simultâneas ainda verãostatus = 'PENDING'
para o dadoid
. Mas a linha está bloqueada para gravação, qualquer transação que queira gravar nela tem que esperar até que o bloqueio seja liberado. Isso acontece quando a transação de bloqueio é confirmada ou revertida. Então, as transações em espera, por sua vez, reavaliarão os filtros e verão o novo estadostatus = 'PROCESSING'
- e ficarão vazias, o que parece ser exatamente o que você quer.Esperar só faz sentido para sua consulta específica se a transação em espera puder encontrar a linha inalterada (ainda pendente) depois que o bloqueio for liberado, o que só acontece em caso de
ROLLBACK
- ou se operações diferentes podem ter bloqueado a linha para gravação.Se você preferir não esperar, use
SKIP LOCKED
orNOWAIT
. Veja: