O que é ACID?
O que é ACID?
Imagine um sistema de compra de ingressos, onde você precisa comprar um ingresso para um show. Você clica em “Comprar”, o sistema verifica se tem ingressos disponíveis, reserva um para você, debita o valor do seu cartão de crédito e gera o ingresso. Agora imagine que, no meio desse processo, o sistema falha. O que acontece com o seu ingresso? E com o seu dinheiro?
Para garantir que isso não aconteça, os bancos de dados relacionais implementam o ACID, que é um conjunto de propriedades que garantem que as transações sejam processadas de forma confiável. Cada propriedade resolve um problema diferente: a Atomicidade garante que nada fique pela metade, a Consistência garante que o banco nunca fique em um estado inválido, o Isolamento garante que compradores simultâneos não se atrapalhem, e a Durabilidade garante que o ingresso comprado não desapareça depois.
Atomicidade
Na Atomicidade uma transação ou é executada por completo ou não é executada. Não existe meio termo. No nosso exemplo, se o sistema falhar no meio do processo, o dinheiro não será debitado do cartão e o ingresso não será gerado. A transação será revertida por completo.
O exemplo clássico é uma transferência entre duas contas, onde o dinheiro sai de uma conta e entra na outra.
1
2
3
4
ActiveRecord::Base.transaction do
david.withdrawal(100)
mary.deposit(100)
end
Esse exemplo só levará dinheiro de David e dará para Mary se nem withdrawal nem deposit levantarem uma exceção. As exceções forçarão um ROLLBACK que retorna o banco de dados ao estado anterior ao início da transação.
Mas, se nem withdrawal nem deposit levantarem uma exceção, então a transação será concluída com sucesso. Ou seja, o dinheiro sairá da conta de David e entrará na conta de Mary.
Consistência
A Consistência garante que a transação leve o banco de dados de um estado válido para outro estado válido. Ou seja, se uma transação não for concluída com sucesso, o banco de dados não será alterado.
No exemplo dos ingressos, isso significa que não é possível comprar um ingresso com uma quantidade negativa ou com dados obrigatórios em branco. O banco rejeita qualquer operação que violaria suas regras.
1
2
3
4
5
6
7
ticket = Ticket.last
ticket.update(id:nil)
# =>
# PG::NotNullViolation: ERROR: null value in column "id" of relation "tickets"
# violates not-null constraint (ActiveRecord::NotNullViolation)
# DETAIL: Failing row contains (null, 2026-04-29 06:01:26.693314, 173335,
# 2026-05-06 19:20:28.89082, 90, 5).
O banco recusa a operação e permanece exatamente como estava, nenhum estado inválido é persistido.
Isolamento
No Isolamento é garantido que transações não interfiram umas nas outras. Vamos voltar ao exemplo dos ingressos. Maria e João estão comprando o último ingresso disponível. Se o isolamento não estiver ativo, João pode ver que há um ingresso disponível e iniciar a compra também. Nesse caso, ambos podem comprar o ingresso, o que não deveria acontecer. Para evitar isso, o isolamento garante que as transações não interfiram umas nas outras. A compra de Maria deve ser concluída antes que a de João possa ser iniciada.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# app/services/ticket_purchase_service.rb
class TicketPurchaseService
class TicketUnavailableError < StandardError; end
def self.purchase(ticket_id:, buyer_name:)
ActiveRecord::Base.transaction(isolation: :serializable) do
# WITH LOCK garante que apenas uma transação por vez acesse esse registro
ticket = Ticket.lock("FOR UPDATE").find(ticket_id)
raise TicketUnavailableError, "Ingresso não disponível!" unless ticket.available?
# Simula o tempo de processamento (onde a corrida aconteceria sem isolamento)
sleep(0.1)
ticket.update!(available: false, owner_name: buyer_name)
puts "[#{buyer_name}] ✅ Compra realizada com sucesso!"
ticket
end
rescue TicketUnavailableError => e
puts "[#{buyer_name}] ❌ #{e.message}"
nil
end
end
Demonstração do problema SEM isolamento (race condition)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def demo_sem_isolamento(ticket_id)
puts "\n=== SEM ISOLAMENTO ==="
# Maria e João leem ao mesmo tempo que o ingresso está disponível
thread_maria = Thread.new do
ticket = Ticket.find(ticket_id)
sleep(0.05) # simula processamento
if ticket.available?
ticket.update!(available: false, owner_name: "Maria")
puts "[Maria] ✅ Compra realizada! (mas João também pode comprar)"
end
end
thread_joao = Thread.new do
ticket = Ticket.find(ticket_id)
sleep(0.05) # lê o mesmo estado "disponível"
if ticket.available?
ticket.update!(available: false, owner_name: "João")
puts "[João] ✅ Compra realizada! (ingresso duplicado 💥)"
end
end
[thread_maria, thread_joao].each(&:join)
end
Com isolamento via FOR UPDATE + transaction serializable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def demo_com_isolamento(ticket_id)
puts "\n=== COM ISOLAMENTO ==="
thread_maria = Thread.new do
TicketPurchaseService.purchase(ticket_id: ticket_id, buyer_name: "Maria")
end
thread_joao = Thread.new do
TicketPurchaseService.purchase(ticket_id: ticket_id, buyer_name: "João")
end
[thread_maria, thread_joao].each(&:join)
end
# Resultado esperado com isolamento:
# [Maria] ✅ Compra realizada com sucesso!
# [João] ❌ Ingresso não disponível!
Durabilidade
Na Durabilidade é garantido que uma vez que uma transação seja concluída com sucesso, ela não será desfeita, mesmo em caso de falha do sistema. No exemplo dos ingressos, uma vez que Maria compra o ingresso, ele não pode ser vendido novamente.
Isso significa que mesmo que o servidor reinicie logo após a compra, o registro da transação de Maria já está gravado permanentemente no banco.
1
2
3
4
5
6
7
8
ActiveRecord::Base.transaction do
ticket.update!(available: false, owner_name: "Maria")
end
# Mesmo que o servidor caia aqui, a compra de Maria está salva.
ticket.reload
puts ticket.owner_name # => "Maria"
puts ticket.available # => false
Conclusão
Voltando ao cenário inicial: você clica em “Comprar”, o sistema processa tudo, mas no meio do caminho algo falha. Graças ao ACID, você não perde dinheiro sem receber o ingresso (Atomicidade), o banco não aceita dados inválidos no processo (Consistência), Maria e João não compram o mesmo ingresso ao mesmo tempo (Isolamento), e uma vez confirmada a compra, ela não some (Durabilidade). As quatro propriedades juntas são o que torna um banco de dados confiável para transações do mundo real.
