Como consultar e aumentar um valor (contador) de maneira segura para threads? (evitar condições de corrida)

10

Em uma tabela em que cada linha tem um contador (apenas um valor inteiro), preciso obter o valor atual e aumentá-lo ao mesmo tempo .

Efetivamente, eu quero fazer isso:

SELECT counter FROM table WHERE id=123
UPDATE table SET counter=counter+1 WHERE id=123

Mas fazer isso como duas consultas obviamente não é seguro para threads: vários processos que fazem a mesma coisa (na mesma linha) podem obter o mesmo valor de contador. Eu preciso que todos sejam únicos, para que cada processo obtenha o valor atual real e o aumente em um.

Posso pensar em uma construção em que implemento um bloqueio manual por linha, mas me pergunto se existe uma maneira mais fácil de fazer isso.

RocketNuts
fonte
usando transações, talvez?
ypercubeᵀᴹ

Respostas:

15

As instruções de atualização funcionam perfeitamente sem a seleção anterior! Como instruções únicas são seguras por definição, mesmo duas consultas UPDATE executadas ao mesmo tempo resultarão na linha incrementada duas vezes.

Se você realmente deseja selecionar o valor para o seu script PHP, faça algo com ele e depois deseje atualizar esse valor exato do contador, faça o seguinte:

BEGIN;
SELECT `counter` FROM `table` WHERE `id` = 123 FOR UPDATE;
UPDATE `table` SET `counter` = `counter`+1 WHERE `id` = 123;
COMMIT;

Isso inicia uma nova transação, seleciona as linhas que você deseja atualizar e as bloqueia exclusivamente. Você pode atualizar com segurança aqueles sem se preocupar com outros clientes alterando seu conteúdo ou mesmo acessando as linhas bloqueadas. Finalmente, você precisa confirmar suas alterações.

Você também deve ler algo sobre os níveis de isolamento . Você provavelmente não deseja um valor como READ UNCOMMITTEDnível de isolamento. Tudo o resto deve ficar bem para este caso de uso.

GhostGambler
fonte
Eu li em outros lugares que o UPDATE table SET counter = counter + 1é suficientemente atômico? Você ainda precisa das instruções de transação que o cercam?
CMCDragonkai
@CMCDragonkai Somente sua consulta é atômica, mas se você selecionar o valor anterior e não usar FOR UPDATEtransações, o valor selecionado poderá ser diferente daquele usado na consulta de atualização. Minha combinação de consultas bloqueia a linha assim que o valor é selecionado e, portanto, garante que esse valor exato do contador seja usado na consulta de atualização.
GhostGambler
Ok, mas isso só é necessário se eu fizer algum trabalho além de incrementar, certo? Tal como está, uma consulta de atualização atômica isolada é boa o suficiente se é tudo o que quero fazer?
CMCDragonkai
11
@CMCDragonkai Se você não executar outra consulta que toque a coluna, estará bem.
29715 GhostGambler