As transações são um conceito fundamental de todo sistema de banco de dados. O ponto essencial da transação é englobar vários passos em uma única operação de tudo ou nada. Os estados intermediários entre os passos não são visíveis para as demais transações simultâneas e, se ocorrer alguma falha que impeça a transação de chegar até o fim, então nenhum dos passos intermediários irá afetar o banco de dados de forma alguma.
Por exemplo, considere um banco de dados de uma instituição financeira contendo o saldo da conta-corrente de vários clientes, assim como o saldo total dos depósitos de cada agência. Suponha que se deseje transferir $100.00 da conta da Alice para a conta do Bob. Simplificando ao extremo, os comandos SQL para essa operação seriam:
UPDATE conta_corrente
SET saldo = saldo - 100.00
WHERE nome = 'Alice';
UPDATE filiais
SET saldo = saldo - 100.00
WHERE nome =
(SELECT nome_filial
FROM conta_corrente
WHERE nome = 'Alice');
UPDATE conta_corrente
SET saldo = saldo + 100.00
WHERE nome = 'Bob';
UPDATE filiais
SET saldo = saldo + 100.00
WHERE nome =
(SELECT nome_filial
FROM conta_corrente
WHERE nome = 'Bob');
Os detalhes desses comandos não são importantes aqui; o ponto importante é o fato de existirem várias atualizações distintas envolvidas para realizar essa operação tão simples. A contabilidade do banco quer ter certeza de que todas essas atualizações foram realizadas, ou que nenhuma delas foi realizada. Com certeza não é interessante que uma falha no sistema faça com que Bob receba $100.00 que não foi debitado da Alice. Além disso, Alice não continuará sendo uma cliente satisfeita se o dinheiro for debitado de sua conta e não for creditado na conta do Bob. É necessário garantir que, caso aconteça algo errado no meio da operação, nenhum dos passos executados até esse ponto irá valer. Agrupar as atualizações em uma transação dá essa garantia. Uma transação é dita como sendo atômica: do ponto de vista das outras transações, ou a transação acontece por inteiro, ou nada acontece.
Desejamos, também, ter a garantia de estando a transação completa e reconhecida pelo sistema de banco de dados, que a mesma fique permanentemente registrada e não seja perdida mesmo no caso de acontecer uma pane logo em seguida. Por exemplo, se estiver sendo registrado um saque em dinheiro pelo Bob não se deseja, de forma alguma, que o débito em sua conta-corrente desapareça devido a uma pane ocorrida logo depois do Bob sair da agência. Um banco de dados transacional garante que todas as atualizações realizadas por uma transação fiquem registradas em meio de armazenamento permanente (ou seja, em disco), antes da transação ser considerada completa.
Outra propriedade importante dos bancos de dados transacionais está muito ligada à noção de atualizações atômicas: quando várias transações estão executando ao mesmo tempo, nenhuma delas deve enxergar as mudanças incompletas efetuadas pelas outras. Por exemplo, se uma transação está ocupada totalizando o saldo de todas as agências, não pode ser visto o débito efetuado na agência da Alice, mas ainda não creditado na agência do Bob, nem o contrário. Portanto, as transações devem ser tudo ou nada, não apenas em termos do efeito permanente no banco de dados, mas também em termos de visibilidade durante o processamento. As atualizações feitas por uma transação em andamento não podem ser vistas pelas outras transações enquanto não terminarem, quando todas as atualizações se tornam visíveis ao mesmo tempo.
No PostgreSQL a transação é definida
envolvendo os comandos SQL da transação pelos
comandos BEGIN e COMMIT.
Sendo assim, a nossa transação bancária ficaria:
BEGIN;
UPDATE conta_corrente
SET saldo = saldo - 100.00
WHERE nome = 'Alice';
-- etc, etc...
COMMIT;
Se no meio da transação for decidido que ela não deverá ser efetivada
(talvez porque tenha sido visto que o saldo da Alice ficou negativo),
poderá ser usado o comando ROLLBACK em vez do
COMMIT para fazer com que todas as atualizações
sejam canceladas.
Na verdade, o PostgreSQL trata todo comando
SQL como sendo executado dentro de uma transação.
Na ausência de um comando BEGIN explícito, então
cada comando possuirá um BEGIN implícito e, se der
tudo certo, um COMMIT, envolvendo-o. Um grupo de
comandos envolvidos por um BEGIN e um
COMMIT é, algumas vezes, chamado de
bloco de transação.
Algumas bibliotecas cliente executam um comando BEGIN
e um comando COMMIT automaticamente, fazendo com
que se obtenha o efeito de um bloco de transação sem perguntar se
isso é desejado. Verifique a documentação da interface utilizada.
É possível controlar os comandos na transação de forma mais
granular utilizando pontos de salvamento
(savepoints). Os pontos de salvamento
permitem descartar partes da transação seletivamente, e efetivar as
demais partes. Após definir o ponto de salvamento por meio do comando
SAVEPOINT, é possível cancelar a transação até o
ponto de salvamento, se for necessário, usando
ROLLBACK TO. Todas as alterações no banco de dados
efetuadas pela transação entre o estabelecimento do ponto de salvamento
e o cancelamento são descartadas, mas as alterações efetuadas antes do
ponto de salvamento são mantidas.
Após desfazer até o ponto de salvamento, este ponto de salvamento continua definido, portanto é possível desfazer várias vezes. Ao contrário, havendo certeza de que não vai ser mais necessário desfazer até o ponto de salvamento, o ponto de salvamento poderá ser liberado, para que o sistema possa liberar alguns recursos. Deve-se ter em mente que liberar, ou desfazer até um determinado ponto de salvamento, libera, automaticamente, todos os pontos de salvamento definidos após o mesmo.
Tudo isso acontece dentro do bloco de transação, portanto nada disso é visto pelas outras sessões do banco de dados. Quando o bloco de transação é efetivado, as ações efetivadas se tornam visíveis como uma unidade para as outras sessões, enquanto as ações desfeitas nunca se tornam visíveis.
Recordando o banco de dados da instituição financeira, suponha que tivesse sido debitado $100.00 da conta da Alice, creditado na conta do Bob, e descoberto em seguida que era para creditar na conta do Wally. Isso poderia ser feito utilizando um ponto de salvamento, conforme mostrado abaixo:
BEGIN;
UPDATE conta_corrente
SET saldo = saldo - 100.00
WHERE nome = 'Alice';
SAVEPOINT meu_ponto_de_salvamento;
UPDATE conta_corrente
SET saldo = saldo + 100.00
WHERE nome = 'Bob';
-- deu ruim ... o certo seria na conta do Wally
ROLLBACK TO meu_ponto_de_salvamento;
UPDATE conta_corrente
SET saldo = saldo + 100.00
WHERE nome = 'Wally';
COMMIT;
Obviamente este exemplo está simplificado ao extremo, mas é possível
efetuar um grau elevado de controle sobre a transação por meio do uso
de pontos de salvamento.
Além disso, a instrução ROLLBACK TO é a única forma
de obter novamente o controle sobre um bloco de transação colocado no
estado interrompido pelo sistema devido a um erro, fora cancelar
completamente e começar tudo de novo.