Skip to content

Commit e98e28b

Browse files
committed
o que e acid
1 parent b236dd6 commit e98e28b

1 file changed

Lines changed: 190 additions & 0 deletions

File tree

_posts/2026-05-06-o-que-acid.md

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
---
2+
layout: post
3+
title: "O que é ACID?"
4+
date: 2026-05-04 11:40:00 -0300
5+
categories: database
6+
tags: [database, sql]
7+
---
8+
9+
# O que é ACID?
10+
11+
Imagine um sistema de compra de ingressos, onde você precisa comprar um ingresso
12+
para um show. Você clica em "Comprar", o sistema verifica se tem ingressos
13+
disponíveis, reserva um para você, debita o valor do seu cartão de crédito e gera
14+
o ingresso. Agora imagine que, no meio desse processo, o sistema falha. O que
15+
acontece com o seu ingresso? E com o seu dinheiro?
16+
17+
Para garantir que isso não aconteça, os bancos de dados relacionais implementam o
18+
ACID, que é um conjunto de propriedades que garantem que as transações sejam
19+
processadas de forma confiável. Cada propriedade resolve um problema diferente:
20+
a Atomicidade garante que nada fique pela metade, a Consistência garante que o
21+
banco nunca fique em um estado inválido, o Isolamento garante que compradores
22+
simultâneos não se atrapalhem, e a Durabilidade garante que o ingresso comprado
23+
não desapareça depois.
24+
25+
## Atomicidade
26+
27+
Na **Atomicidade** uma transação ou é executada por completo ou não é executada.
28+
Não existe meio termo. No nosso exemplo, se o sistema falhar no meio do processo,
29+
o dinheiro não será debitado do cartão e o ingresso não será gerado. A transação
30+
será revertida por completo.
31+
32+
O exemplo clássico é uma transferência entre duas contas, onde o dinheiro sai de
33+
uma conta e entra na outra.
34+
35+
{% highlight ruby %}
36+
ActiveRecord::Base.transaction do
37+
david.withdrawal(100)
38+
mary.deposit(100)
39+
end
40+
{% endhighlight %}
41+
42+
Esse exemplo só levará dinheiro de David e dará para Mary se nem withdrawal nem
43+
deposit levantarem uma exceção. As exceções forçarão um ROLLBACK que retorna o
44+
banco de dados ao estado anterior ao início da transação.
45+
46+
Mas, se nem withdrawal nem deposit levantarem uma exceção, então a transação será
47+
concluída com sucesso. Ou seja, o dinheiro sairá da conta de David e entrará na
48+
conta de Mary.
49+
50+
## Consistência
51+
52+
A **Consistência** garante que a transação leve o banco de dados de um estado
53+
válido para outro estado válido. Ou seja, se uma transação não for concluída com
54+
sucesso, o banco de dados não será alterado.
55+
56+
No exemplo dos ingressos, isso significa que não é possível comprar um ingresso
57+
com uma quantidade negativa ou com dados obrigatórios em branco. O banco rejeita
58+
qualquer operação que violaria suas regras.
59+
60+
{% highlight ruby %}
61+
ticket = Ticket.last
62+
ticket.update(id:nil)
63+
# =>
64+
# PG::NotNullViolation: ERROR: null value in column "id" of relation "tickets"
65+
# violates not-null constraint (ActiveRecord::NotNullViolation)
66+
# DETAIL: Failing row contains (null, 2026-04-29 06:01:26.693314, 173335,
67+
# 2026-05-06 19:20:28.89082, 90, 5).
68+
{% endhighlight %}
69+
70+
O banco recusa a operação e permanece exatamente como estava — nenhum estado
71+
inválido é persistido.
72+
73+
## Isolamento
74+
75+
No **Isolamento** é garantido que transações não interfiram umas nas outras.
76+
Vamos voltar ao exemplo dos ingressos. Maria e João estão comprando o último
77+
ingresso disponível.
78+
Se o isolamento não estiver ativo, João pode ver que há um ingresso disponível e
79+
iniciar a compra também. Nesse caso, ambos podem comprar o ingresso, o que não
80+
deveria acontecer. Para evitar isso, o isolamento garante que as transações não
81+
interfiram umas nas outras. A compra de Maria deve ser concluída antes que a de
82+
João possa ser iniciada.
83+
84+
{% highlight ruby %}
85+
# app/services/ticket_purchase_service.rb
86+
class TicketPurchaseService
87+
class TicketUnavailableError < StandardError; end
88+
89+
def self.purchase(ticket_id:, buyer_name:)
90+
ActiveRecord::Base.transaction(isolation: :serializable) do
91+
# WITH LOCK garante que apenas uma transação por vez acesse esse registro
92+
ticket = Ticket.lock("FOR UPDATE").find(ticket_id)
93+
94+
raise TicketUnavailableError, "Ingresso não disponível!" unless ticket.available?
95+
96+
# Simula o tempo de processamento (onde a corrida aconteceria sem isolamento)
97+
sleep(0.1)
98+
99+
ticket.update!(available: false, owner_name: buyer_name)
100+
101+
puts "[#{buyer_name}] ✅ Compra realizada com sucesso!"
102+
ticket
103+
end
104+
rescue TicketUnavailableError => e
105+
puts "[#{buyer_name}] ❌ #{e.message}"
106+
nil
107+
end
108+
end
109+
{% endhighlight %}
110+
111+
Demonstração do problema SEM isolamento (race condition)
112+
113+
{% highlight ruby %}
114+
def demo_sem_isolamento(ticket_id)
115+
puts "\n=== SEM ISOLAMENTO ==="
116+
117+
# Maria e João leem ao mesmo tempo que o ingresso está disponível
118+
thread_maria = Thread.new do
119+
ticket = Ticket.find(ticket_id)
120+
sleep(0.05) # simula processamento
121+
if ticket.available?
122+
ticket.update!(available: false, owner_name: "Maria")
123+
puts "[Maria] ✅ Compra realizada! (mas João também pode comprar)"
124+
end
125+
end
126+
127+
thread_joao = Thread.new do
128+
ticket = Ticket.find(ticket_id)
129+
sleep(0.05) # lê o mesmo estado "disponível"
130+
if ticket.available?
131+
ticket.update!(available: false, owner_name: "João")
132+
puts "[João] ✅ Compra realizada! (ingresso duplicado 💥)"
133+
end
134+
end
135+
136+
[thread_maria, thread_joao].each(&:join)
137+
end
138+
{% endhighlight %}
139+
140+
Com isolamento via FOR UPDATE + transaction serializable
141+
142+
{% highlight ruby %}
143+
def demo_com_isolamento(ticket_id)
144+
puts "\n=== COM ISOLAMENTO ==="
145+
146+
thread_maria = Thread.new do
147+
TicketPurchaseService.purchase(ticket_id: ticket_id, buyer_name: "Maria")
148+
end
149+
150+
thread_joao = Thread.new do
151+
TicketPurchaseService.purchase(ticket_id: ticket_id, buyer_name: "João")
152+
end
153+
154+
[thread_maria, thread_joao].each(&:join)
155+
end
156+
157+
# Resultado esperado com isolamento:
158+
# [Maria] ✅ Compra realizada com sucesso!
159+
# [João] ❌ Ingresso não disponível!
160+
{% endhighlight %}
161+
162+
## Durabilidade
163+
164+
Na **Durabilidade** é garantido que uma vez que uma transação seja concluída com
165+
sucesso, ela não será desfeita, mesmo em caso de falha do sistema. No exemplo dos
166+
ingressos, uma vez que Maria compra o ingresso, ele não pode ser vendido novamente.
167+
168+
Isso significa que mesmo que o servidor reinicie logo após a compra, o registro
169+
da transação de Maria já está gravado permanentemente no banco.
170+
171+
{% highlight ruby %}
172+
ActiveRecord::Base.transaction do
173+
ticket.update!(available: false, owner_name: "Maria")
174+
end
175+
176+
# Mesmo que o servidor caia aqui, a compra de Maria está salva.
177+
ticket.reload
178+
puts ticket.owner_name # => "Maria"
179+
puts ticket.available # => false
180+
{% endhighlight %}
181+
182+
## Conclusão
183+
184+
Voltando ao cenário inicial: você clica em "Comprar", o sistema processa tudo —
185+
e no meio do caminho algo falha. Graças ao ACID, você não perde dinheiro sem
186+
receber o ingresso (Atomicidade), o banco não aceita dados inválidos no processo
187+
(Consistência), Maria e João não compram o mesmo ingresso ao mesmo tempo
188+
(Isolamento), e uma vez confirmada a compra, ela não some (Durabilidade). As
189+
quatro propriedades juntas são o que torna um banco de dados confiável para
190+
transações do mundo real.

0 commit comments

Comments
 (0)