Transações e pool
Tudo ou nada — e por que 'transacao' com bloco é a única forma segura.
Transações#
Uma transação garante que um conjunto de escritas aconteça inteiro, ou não aconteça:
dataforge
adopt Forge
db := Forge.conectar(":memory:")
Forge.executar(db, "create table contas (id integer primary key, saldo real)")
Forge.de(db, "contas").inserir([{"id": 1, "saldo": 100.0}, {"id": 2, "saldo": 0.0}])
action transferir(db, de, para, valor):
Forge.de(db, "contas").onde("id", de).incrementar("saldo", 0 - valor)
Forge.de(db, "contas").onde("id", para).incrementar("saldo", valor)
yield yes
Forge.transacao(db, lambda c => transferir(c, 1, 2, 30.0))
assert Forge.de(db, "contas").onde("id", 1).primeiro()["saldo"] is 70.0
assert Forge.de(db, "contas").onde("id", 2).primeiro()["saldo"] is 30.0Se o corpo falhar, tudo é desfeito:
dataforge
adopt Forge
db := Forge.conectar(":memory:")
Forge.executar(db, "create table t (id integer primary key, n integer)")
action falha(c):
Forge.de(c, "t").inserir({"n": 1})
trigger "deu errado no meio"
monitor:
Forge.transacao(db, falha)
handle e:
assert e.message is "deu errado no meio"
// a linha inserida antes da falha não ficou
assert Forge.de(db, "t").contar() is 0A forma manual#
Existe, para quando o fluxo não cabe num bloco:
dataforge
adopt Forge
db := Forge.conectar(":memory:")
Forge.executar(db, "create table t (id integer primary key, n integer)")
db.comecar()
monitor:
Forge.de(db, "t").inserir({"n": 1})
db.confirmar()
handle e:
db.desfazer()
assert Forge.de(db, "t").contar() is 1Pool de conexões#
Abrir conexão custa: TCP, autenticação, negociação. Numa rota web isso acontece por requisição e passa a dominar o tempo de resposta.
dataforge
adopt Forge
pool := Forge.pool(":memory:", 5)
with Forge.conexao(pool) as db:
out Forge.versao(db)
out pool.estado()O pool não é só cache — é também limite. Sem teto, um pico de tráfego abre mil conexões e o servidor recusa todas, inclusive as do que já estava funcionando. Melhor a milésima requisição esperar do que as mil falharem.
Níveis de isolamento#
dataforge
db.comecar("SERIALIZABLE")| Nível | Impede | Custo |
|---|---|---|
READ COMMITTED | leitura suja | baixo — o padrão no PostgreSQL |
REPEATABLE READ | leitura não repetível | médio — o padrão no MySQL |
SERIALIZABLE | leitura fantasma | alto — pode abortar por conflito |
Com SERIALIZABLE, prepare-se para TransactionError por falha de serialização, e para tentar de novo. É o preço da garantia.