Você está na página 1de 6

Controle de Transações no PostGreSQL – prof Fábio Dal Osto

Para iniciar uma transação no PostgreSQL devemos utilizar o comando BEGIN. Caso
ele não seja fornecido o banco vai se comportar de forma padrão (autocommit), ou
seja, cada comando é considerado uma transação.

Sitaxe:

Esse comando inicia uma transação:

BEGIN [ WORK | TRANSACTION ] [ transaction_mode [, ...] ]

onde o modo de transação é um desses:

ISOLATION LEVEL { SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ


UNCOMMITTED }
READ WRITE | READ ONLY

Esse comando finaliza uma transação com sucesso:

COMMIT [ WORK | TRANSACTION ]

Esse comando finaliza uma transação cancelando seus resultados:

ROLLBACK [ WORK | TRANSACTION ]

Esse comando define um novo ponto de salvamento (salvamento intermediário)


dentro de uma transação:

SAVEPOINT savepoint_name

exemplo:

CREATE TABLE TABELA_01 (ID INT);

BEGIN;
INSERT INTO TABELA_01 VALUES (1);
SAVEPOINT PONTO_SALVAMENTO;
INSERT INTO TABELA_01 VALUES (2);
ROLLBACK TO SAVEPOINT PONTO_SALVAMENTO;
INSERT INTO TABELA_01 VALUES (3);
COMMIT;

SELECT * FROM TABELA_01;

A transação acima irá inserir os valores 1 e 3, mas não 2;

Para estabelecer e depois destruir um savepoint:

BEGIN;
INSERT INTO TABELA_01 VALUES (2);
SAVEPOINT PONTO_SALVAMENTO;
INSERT INTO TABELA_01 VALUES (4);
RELEASE SAVEPOINT PONTO_SALVAMENTO;
COMMIT;
SELECT * FROM TABELA_01;
Controle de concorrência no PostGreSQL

O PostGreSQL mantêm a consistência de dados através da utilização do conceito


MVCC (Multiversion Concurrency Control).
Esse modelo funciona da seguinte forma:

Cada transação ocorrendo em um banco de dados vê um snapshot dos dados


envolvidos nessa transação, ou seja, a última versão consistente dos dados antes
do início dessa transação.

Essa técnica protege a transação corrente de acessar dados inconsistentes que


poderiam ser gerados por outras transações concorrentes sobre os mesmos dados
fornecendo isolamento de transação para cada sessão de banco de dados.

A principal vantagem de usar o modelo MVCC em relação ao uso de bloqueios


(locks) é que os locks concedidos para leitura não entrarão em conflito com os
locks concedidos para escrita, dessa forma uma leitura nunca bloqueia uma
escrita e uma escrita nunca bloqueia uma leitura.

Níveis de Isolamento no PostGreSQL


-------------------------------------
Utilizando o PostgreSQL você pode escolher qualquer um dos 4 níveis de
isolamento existentes, porém internamente somente dois níveis são utilizados:
read committed e serializable.

Portanto, quando você define o nível de transação para read uncommitted você
está, na verdade, selecionando o nível de isolamento read committed.

E quando você seleciona o nível de isolamento Repeatable Read você está


selecionando, na verdade, o nível de isolamento serializable.

Isso é feito, essencialmente, por que esse é o único jeito de tornar o padrão de
níveis de isolamento compatíveis com o modelo MVCC.

Comando para definir o nível de isolamento de uma transação:

SET TRANSACTION ISOLATION LEVEL { SERIALIZABLE | REPEATABLE READ | READ


COMMITTED | READ UNCOMMITTED } READ WRITE | READ ONLY

O default é READ COMMITTED READ WRITE.

Esse comando deve aparecer após um comando BEGIN ou START TRANSACTION e vale
somente para a transação corrente;
Read Committed
-----------------

O comando corrente vê a última versão dos dados antes da sua execução, o comando
seguinte pode pegar uma outra versão atualizada. Resumindo, a cada nova consulta
um novo snapshot é requisitado ao banco de dados e ele reflete todos os dados
“comitados” até aquele momento para a consulta em execução.

Por exemplo: um comando SELECT somente vê os dados “comitados” antes da execução


da consulta. Aqueles dados que forem alterados ou “comitados” durante a consulta
não terão seus novos valores listados no resultado.

Algumas regras de funcionamento importantes:

1: alterações feitas por outros comandos (INSERT/UPDATE/DELETE) dentro da mesma


transação e anteriores ao comando SELECT terão seus dados apresentados pela
consulta, mesmo não estando “comitados”.

2: (UPDATE, DELETE, SELECT FOR UPDATE): imaginemos a seguinte situação:

t1: (iniciar uma transação em uma janela SQL do pgadmin)


Begin;
update cd set vlpreco = vlpreco * 1.5 where codigo_cd > 10;

e concorrentemente iniciamos uma nova transação:

t2: (iniciar outra transação em uma nova janela SQL do pgadmin)


Begin;
update cd set vlpreco = vlpreco * 1.1 where codigo_cd > 15;

o que acontecerá?

A transação T1 (que iniciou antes) fará uma pesquisa localizando todos os cds
cujo código seja maior que 10 e conseguirá, para eles, um bloqueio de
atualização.

A transação T2 (que iniciou depois) fará uma pesquisa por todos os cds cujos
cujo código seja maior que 15, porém não conseguirá alguns bloqueios já que os
registros estão bloqueados/marcados pela primeira transação e irá entrar em
espera (wait).

A transação T1 fará a atualização dos dados e então a transação T2 irá se


comportar conforme o resultado dela:

a) se houve rollback ao final de T1: a transação T2 será executada normalmente;


b) se houve commit ao final de T1: a transação T2 deverá refazer a pesquisa para
garantir que não há houve alteração nos dados que devem ser atualizados
(exclusão ou atualização) e então irá prosseguir com a atualização dos mesmos.

No exemplo acima uma transação T12 fosse disparada entre os T1 e T2 mas apenas
com o objetivo de leitura leria a última versão atualizada dos dados. Se T1 já
houvesse concluído com sucesso ela leria o valor dos cds atualizados em 50%,
caso contrário o seu valor antes do fim da transação. Observe que mesmo durante
a transação T2 somente os valores atualizados em T1 é que são obtidos.

Serializable
----------------
todos os comandos da transação vêem a mesma versão dos dados. Aquela versão
obtida antes do início dessa transação. Resumindo, todas as consultas executadas
dentro de uma transação desse tipo devolvem sempre o mesmo resultado não havendo
a criação de um novo snapshot por comando.

Algumas regras de funcionamento importantes:

1: alterações feitas por outros comandos (INSERT/UPDATE/DELETE) dentro da mesma


transação e anteriores ao comando SELECT terão seus dados apresentados pela
consulta, mesmo não estando comitados.

2: (UPDATE, DELETE, SELECT FOR UPDATE):

t1:
-- T1
Begin;
update rec_fatura set valor_total = valor_total * 1.5 where idfatura = 1;

e concorrentemente iniciamos uma nova transação:


t2:
--T2
Begin;
update rec_fatura set valor_total = valor_total * 2.0 where idfatura = 1;

o que acontecerá?

A transação T1 (que iniciou antes) fará uma pesquisa localizando a fatura de


identificação igual a 1 e conseguirá, para ela, um bloqueio de atualização.

A transação T2 (que iniciou depois) fará a mesma pesquisa, porém, não conseguirá
um bloqueio exclusivo já que o registro está bloqueado/marcado pela primeira
transação e irá entrar em espera (wait).

A transação T1 fará a atualização dos dados e então a transação T2 irá se


comportar conforme o resultado dela.

-- T1
Begin;
-- T1
select * from rec_fatura join rec_cliente using (idcliente) where idfatura = 1;

-- execute T2
update rec_fatura set valor_total = valor_total + 5.0 where idfatura = 1;

select * from rec_fatura join rec_cliente using (idcliente) where idfatura = 1;

--T2
Begin;
--T2
update rec_fatura set valor_total = valor_total + 10.0 where idfatura = 1;

-- volte para T1 e execute o update, depois volte aqui e faça novamente a


consulta
Consultando dados e os bloqueando para atualização futura

É possível bloquear explicitamente apenas as linhas afetadas pelo comando


SELECT. Para isso devemos utilizar as palavras-chave FOR UPDATE e FOR SHARED
junto ao comando SELECT.

Exemplos:

SELECT nome, idade


From Alunos
Where matricula = 50000
FOR UPDATE

SELECT nome, idade


From Alunos
Where matricula = 50000
FOR SHARE

É possível, ainda, incluir a palavra-chave NOWAIT para impedir que um comando


fique aguardando indefinidamente pela conclusão de outro devido a
incompatibilidade de bloqueios.

Lembrar que bloquear explicitamente registros pode causar escrita em disco


devido à necessidade de alterar as linhas bloqueadas marcando-as com o tipo de
bloqueio obtido.

-- T1
Begin;
-- T1
select * from rec_fatura join rec_cliente using (idcliente) where idfatura = 1
FOR UPDATE;

-- execute T2
update rec_fatura set valor_total = valor_total + 5.0 where idfatura = 1;

select * from rec_fatura join rec_cliente using (idcliente) where idfatura = 1;

--T2
Begin;
--T2
update rec_fatura set valor_total = valor_total + 10.0 where idfatura = 1;
DeadLocks
--------------

T1

BEGIN;

UPDATE rec_fatura SET valor_total = valor_total + 1 WHERE idFatura = 1;


--(ok lock no registro 1)

T2

BEGIN;

UPDATE rec_cliente SET total_fatura = total_fatura + 1 WHERE idcliente = 1;


--(ok lock no registro 1)

T1

UPDATE rec_cliente SET total_fatura = total_fatura - 1 WHERE idcliente = 1;

T2

UPDATE rec_fatura SET valor_total = valor_total - 1 WHERE idFatura = 1; --


PROBLEMA!!! Já está bloqueado por T1

Aqui ocorre o deadlock.

Você também pode gostar