|
| 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