Eu tenho que tabelas:
users (id serial, coins integer not null default 0)
purchases (id serial, price integer not null, user_id)
Quando uma nova linha é inserida na purchases
tabela, quero subtrair amount
da user.coins
coluna e garantir que user.coins >= 0
(para que o usuário tenha fundos suficientes para fazer uma compra).
Eu quero fazer isso no AFTER INSERT
gatilho:
UPDATE users SET coins = coins - NEW.price WHERE id = NEW.user_id;
RETURN NULL;
Minha pergunta é como garantir que o usuário não gaste mais moedas do que possui? Minha primeira ideia é apenas fazer CHECK
restrições na users
tabela como esta CHECK (coins >= 0)
- isso funcionará com várias inserções simultâneas na users
tabela?
Você tem que ter muito cuidado em casos como esses. O nível de isolamento de transação padrão que o PostgreSQL usa é
READ COMMITTED
, e nesse modo você pode facilmente obter casos desagradáveis em que duas transações estão realizando verificações como esta:Se quiser ver a condição de corrida em ação, execute o mesmo SQL em duas sessões do psql ao mesmo tempo. Você verá que ambas as sessões pensarão que o usuário tem moedas suficientes para comprar um item e ambas registrarão uma compra. (Observe que exatamente o mesmo problema existiria com uma restrição CHECK na
users
tabela comREAD COMMITTED
transações.)Como corrigir essa condição de corrida? Leia sobre os níveis de isolamento de transações , você provavelmente deseja usar
SERIALIZABLE
para códigos de contabilidade importantes, como verificar saldos de contas e registrar compras. Outra opção é usar SELECT ... FOR UPDATE para obter um bloqueio em nível de linha, digamos, nausers
tabela enquanto você verifica o saldo da conta, o que garantirá que duas transações não possam estar executando a mesma verificação delicada no mesmo tempo.