Normalmente, a solução para esses problemas de simultaneidade envolve transações e bloqueio otimista :quando você atualizar o contador, adicione um
where
cláusula para verificar o valor antigo e contar o número de linhas atualizadas. v = select value from counter where id=x.
update counter set value = v+1 where value = v and id=x
Se o contador foi atualizado nesse meio tempo, a atualização não alterará nenhuma linha, então você sabe que precisa reverter e tentar mais uma vez a transação.
Um problema é que isso pode levar a uma alta contenção , com apenas algumas transações bem-sucedidas e muitas falhas.
Então pode ser melhor manter o bloqueio pessimista , onde você bloqueia a linha primeiro e depois a atualiza. Mas apenas um benchmark lhe dirá.
EDITAR
Se você usar transação sem bloqueio otimista, o cenário a seguir pode acontecer.
Max authorized = 50. Current value = 49.
T1: start tx, read value --> 49
T2: start tx, read value --> 49
T1: update value --> 50, acquire a row lock
T1: commits --> release the lock
T2: update value --> 50, acquire a row lock
T2: commits --> release the lock
Ambas as transações são bem-sucedidas, o valor é 50, mas há uma inconsistência.