Pular para o conteúdo

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

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

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

Pool 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ívelImpedeCusto
READ COMMITTEDleitura sujabaixo — o padrão no PostgreSQL
REPEATABLE READleitura não repetívelmédio — o padrão no MySQL
SERIALIZABLEleitura fantasmaalto — 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.