<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Blog da Especializa &#187; mvcc</title>
	<atom:link href="https://especializa.com.br/blog/tag/mvcc/feed/" rel="self" type="application/rss+xml" />
	<link>https://especializa.com.br/blog</link>
	<description>Novidades, curiosidades e nosso ponto-de-vista sobre o mercado de TI</description>
	<lastBuildDate>Sat, 04 May 2013 07:55:07 +0000</lastBuildDate>
	<language>pt-BR</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	
		<item>
		<title>Transacoes no PostgreSQL</title>
		<link>https://especializa.com.br/blog/2010/06/27/transacoes-no-postgresql/</link>
		<comments>https://especializa.com.br/blog/2010/06/27/transacoes-no-postgresql/#comments</comments>
		<pubDate>Sun, 27 Jun 2010 03:39:44 +0000</pubDate>
		<dc:creator>especializa</dc:creator>
				<category><![CDATA[tutoriais]]></category>
		<category><![CDATA[acid]]></category>
		<category><![CDATA[mvcc]]></category>
		<category><![CDATA[postgresql]]></category>
		<category><![CDATA[transacoes]]></category>

		<guid isPermaLink="false">http://especializa.com.br/blog/?p=155</guid>
		<description><![CDATA[Prometi aos alunos da turma de PostgreSQL Developer do meu amigo Aercio que iria de tratar aqui do modelo de transações implementado no PostgreSQL. Como resolvi atualizar o material sobre PostgreSQL que venho escrevendo para a versão 9.0, decidi já testar os exemplos aqui no 9.0 beta 2 recém disponibilizado para download no site oficial. [...]]]></description>
				<content:encoded><![CDATA[<p>Prometi aos alunos da turma de PostgreSQL Developer do meu amigo Aercio que iria de tratar aqui do modelo de transações implementado no PostgreSQL. Como resolvi atualizar o material sobre PostgreSQL que venho escrevendo para a versão 9.0, decidi já testar os exemplos aqui no 9.0 beta 2 recém disponibilizado para download <a href="http://wwwmaster.postgresql.org/download/mirrors-ftp/source/v9.0beta2/postgresql-9.0beta2.tar.bz2" target="_blank">no site oficial</a>. Para uma ambientação inicial no PostgreSQL, não deixe de ler o primeiro capítulo do provável livro (ainda não atualizado) <a href="http://especializa.com.br/blog/2010/05/18/material-de-postgresql-administrator/" target="_blank">disponível por aqui</a>.<br />
<span id="more-155"></span><br />
<h2>O que são transações?</h2>
<p>Transações são conjuntos de instruções enviadas ao banco de dados que devem ser tratadas com uma única operação. Ou o servidor realiza tudo ou não realiza nada. O exemplo clássico usado em 110% dos cursos de bancos de dados é a famosa transação financeira de transferência de fundos. Imagine que Mônica deseje transferir 100,00 reais para a conta de Cebolinha. Esta operação na realidade se divide em duas. Um débito na conta de Mônica e um crédito de mesmo valor  na conta de Cebolinha (a ordem não interfere). Agora, se logo após o débito na primeira conta, o servidor sair do ar antes de que possa executar a segunda. Para onde foram os 100,00 reais?</p>
<p>Existem quatro características que os SGBDs devem garantir se pretendem lidar com transações com segurança. O conjunto dessas características é conhecido como ACID (atomicity, consistency, isolation, durability). Vejamos o que significam:</p>
<h3>Atomicidade</h3>
<p>É o ponto chave. É quem determina o tudo-ou-nada. A comunidade científica por longos anos acreditou que um átomo seria a unidade mínima da matéria, sólido e indivisível. Já se sabe em simples aulas de química que essa idéia caiu há séculos. No entanto, este conceito serve de analogia para ocasiões onde deve se considerar a impossibilidade de dividir algo em unidades menores.</p>
<p>Sendo assim, a atomicidade é a característica que torna um conjunto de operações um agrupamento indivisível executado por completo ou nada feito.</p>
<h3>Consistência</h3>
<p>Esta característica determina que o SGBD deve iniciar uma transação em um estado consistente e terminá-la deixando os dados em outro estado consistente. Caso alguma ação dentro de uma transação dê problemas e fira alguma regra de consistência de dados (como uma falha de escrita em disco, por exemplo), toda a transação deve ser completamente abortada, deixando os dados em seu estado consistente imediatamente anterior. O ato de abortar e voltar ao ponto inicial é conhecido como ROLLBACK.</p>
<p>Inconsistências não se resumem a problemas físicos de dados. É possível encarar uma situação fisicamente consistente como logicamente inconsistente. No exemplo dos desaparecimento dos 100,00 reais, o banco ficou em um estado inconsistente uma vez que se somarmos os saldos nas duas contas, independentemente do valor transferido os resultados pré e pós transferência deveria dar consistentemente o mesmo.</p>
<h3>Isolamento</h3>
<p>Esta característica remete a acessos concorrentes. Caso apenas uma transação seja executada no SGBD em determinado intervalo de tempo, o isolamento não vai lhe acrescentar nada. No entanto, como sabemos que SGBDs, na maioria dos casos, são usados por sistemas concorrentes, esta característica entra para garantir que informações de uma transação não se misturem com de outras.</p>
<p>Esta é a única característica relativa das quatro. Ou seja, é a única que não necessariamente é ruim quando infringida de alguma forma. Por um lado, seria ruim dados de uma transação estarem visíveis a outras como no problema clássico conhecido como Dirty Read que ocorre quando uma transação tx1 modifica alguns registros, uma transação tx2 ao selecionar alguns dados, visualiza esses registros alterados por tx1, mas esta primeira é abortada por alguma razão. Em resumo, tx2 leu dados errados, uma vez que eles nunca deveriam ter sido alterados.</p>
<p>Por outro lado, a capacidade de trabalhar com múltiplas transações ao mesmo tempo pode garantir o bom desempenho em sistemas muito acessados, uma vez que uma não fica esperando pela conclusão da outra. Elas são implementadas em ambientes multi-processos ou multi-threads e podem explorar melhor o pontencial multi-core dos hardwares atuais.</p>
<h3>Durabilidade</h3>
<p>Toda transação é encerrada com a execução da ação COMMIT. Esta característica é a garantia dada pelo SGBD que quando retornar um sinal COMMIT, os dados já devem estar gravados no meio persistente e ele deve ser capaz de recuperar esses dados em transações posteriores. Caso haja algum problema, a transação deve ser abortada. Conforme a regra da consistência, caso não haja problemas, o banco está jurando de pés juntos que gravou e você vai poder ler depois.</p>
<p>Como tratado no capítulo 1, o PostgreSQL escreve em disco registros de log WAL e assim que estejam completamente escritos, retorna o sinal COMMIT. Já no capítulo 2, tratamos das diretivas do postgresql.conf <em>fsync</em> e <em>wal_sync_method</em>. Para recapitular, a primeira faz com que o SGBD após a execução do comando de escrita do segmento WAL realize consultas ao sistema operacional a fim de obter a garantia de que o valor escrito está completamente descarregado no disco. Já a segunda determina o método adotado nesta pesquisa. Desligar a diretiva fsync pode proporcionar um maior desempenho, mas o risco da falta de durabilidade de uma transação &#8220;commitada&#8221; em geral não faz valer a pena esse tuning desesperado.</p>
<p>O método de sincronização com o disco de segmentos WAL (<em>wal_sync_method</em>) padrão do Linux é o <em>fdatasync</em>. O método <em>fsync</em> aguarda o sistema operacional descarregar em disco toda a pilha do buffer de I/O e atualizar o inode do sistema de arquivos com a informação de última alteração do arquivo escrito. O <em>fdatasync</em> não espera pela atualização deste metadado. Ele se preocupa apenas com o que mais importa que é a atualização propriamente dita dos dados.</p>
<h2>Transações e Write-ahead Logging</h2>
<p>Ao confirmar cada transação, o servidor não registra em disco as páginas de dados (aqueles 8Kb que ficam na memória). É bem menos custoso registrar neste momento (e esperar pelo fsync), um registro de segmento WAL uma vez que ele é linear (alterações na base real podem envolver dados que não necessariamente estejam na mesma página de dados) e por isso realizam movimentos menores da controladora de disco, além de não requerer pesquisa por espaço livre (FSM).</p>
<p>Por outro lado, a manipulação das páginas de memória compartilhada que são carregadas do e para o disco é bem mais vantajoso do que sair vasculhando registros de log de operações a fim de obter os dados necessários nas consultas.</p>
<p>Sendo assim, o PostgreSQL precisa realizar essa tradução do que é salvo em log para as páginas de dados de tempos em tempos. Este processo de escrita em background (bgwriter) é denominado CHECKPOINT. No PostgreSQL, checkpoints não têm ligação direta com o COMMIT de uma transação, mas achei interessante ilustrar o que deve ocorrer com algumas transações confirmadas entre um checkpoint e um provável crash da base.</p>
<p>O vídeo abaixo mostra o que ocorre com 5 transações considerando que em um momento distinto haverá uma ação de checkpoint e posteriormente haverá um crash do servidor, provocando sua queda inesperada. Caso você não consiga ver o vídeo, clique para baixá-lo em <a href="http://www.especializa.com.br/blog/wp-content/uploads/2010/06/transacoes.ogv">Ogv</a> (theora) ou <a href="http://www.especializa.com.br/blog/wp-content/uploads/2010/06/transacoes.mp4">MP4</a> (H.264).<br />
<video width="500" height="375" controls><br />
<source src="http://www.especializa.com.br/blog/wp-content/uploads/2010/06/transacoes.ogv"/><br />
<source src="http://www.especializa.com.br/blog/wp-content/uploads/2010/06/transacoes.mp4"/><br />
Seu browser não suporta vídeos HTML5. Operamor de Deus, chreima ele no firefoxgo.<br />
</video></p>
<h2>Problemas de isolamento</h2>
<p>Transações já são interessantes mesmo em cenário standalone, uma vez que é possível desfazer todo um bloco de procedimentos apenas com um simples Rollback. Mas é em sistemas concorrentes que o bicho pega. Caso o isolamento não seja garantido alguns problemas podem ocorrer.</p>
<h3>Dirty read</h3>
<p>O primeiro deles já foi apontado. Dirty Read ou leitura suja acontece quando uma transação lê algum dado alterado por outra que acaba não confirmando suas alterações. É um problema extremo que de fato não é comum que bancos de dados permitam que ocorra. O PostgreSQL não permite. Confira no exercício abaixo. Primeiramente, vamos criar a base para os testes:</p>
<pre class="brush: sql;">

postgres=# CREATE DATABASE exercicio;
CREATE DATABASE
postgres=# \c exercicio
You are now connected to database &quot;exercicio&quot;.
exercicio=# create table aluno (id serial primary key, nome varchar(50));
NOTICE:  CREATE TABLE will create implicit sequence &quot;aluno_id_seq&quot; for serial column &quot;aluno.id&quot;
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index &quot;aluno_pkey&quot; for table &quot;aluno&quot;
CREATE TABLE
exercicio=# INSERT into aluno (nome) values ('Huguinho'), ('Zezinho'), ('Luisinho'), ('Juquinha'), ('Joaozinho');
INSERT 0 5
</pre>
<p>Criamos 5 alunos (só peça boa =) em uma nova base. Agora vamos ao primeiro cenário. Para isto será necessário que você abra duas instâncias do psql ou qualquer outra ferramenta cliente (que certamente não será tão limpa e cheirosa quanto o psql).</p>
<p>Inicie uma transação em cada uma executando em ambas a instrução:</p>
<pre class="brush: sql;">

exercicio=# begin;
BEGIN

</pre>
<p>Na primeira, execute o seguinte comando:</p>
<pre class="brush: sql;">

exercicio=# UPDATE aluno set nome = 'Jose da Silva' where id = 2;
UPDATE 1

exercicio=# select * from aluno;
id |     nome
----+---------------
1 | Huguinho
3 | Luisinho
4 | Juquinha
5 | Joaozinho
2 | Jose da Silva
(5 rows)

</pre>
<p>Veja que o nome de Zezinho agora é Jose da Silva.</p>
<p>Realize a mesma consulta na segunda instância do psql:</p>
<pre class="brush: sql;">

exercicio=# select * from aluno;
id |   nome
----+-----------
1 | Huguinho
2 | Zezinho
3 | Luisinho
4 | Juquinha
5 | Joaozinho
(5 rows)

</pre>
<p>Como você atentou, os alunos continuam com seus nomes intactos. O que a primeira transação modificou não foi sentido pela segunda. O motivo é que se a primeira voltar atrás, a segunda já teria lido e tomado suas próprias decisões baseando-se em dados inconsistentes. Confira como faríamos para reverter (execute na primeira transação):</p>
<pre class="brush: sql;">

exercicio=# ROLLBACK ;
ROLLBACK
exercicio=# select * from aluno;
id |   nome
----+-----------
1 | Huguinho
2 | Zezinho
3 | Luisinho
4 | Juquinha
5 | Joaozinho
(5 rows)

</pre>
<p>Nossa base voltou ao estágio em que estava no momento do BEGIN.</p>
<h3>Unrepeatable Reads</h3>
<p>Leituras não repetíveis acontecem quando uma transação lê algum dado em determinado momento, posteriormente outra transação altera ou deleta e executa o COMMIT. A primeira volta a ler os mesmos dados e constata que eles não estão mais como antes. Este não chega a ser um problema em muitos sistemas, a ponto do PostgreSQL, em seu nível de isolamento padrão, permitir que ocorra. Vejamos:</p>
<p>PSQL1: Iniciamos uma nova transação (aquela anterior foi desfeita com ROLLBACK) e atualizamos novamente o nome de Zezinho.</p>
<pre class="brush: sql;">

exercicio=# BEGIN ;
BEGIN
exercicio=# UPDATE aluno set nome = 'Jose da Silva' where id = 2;
UPDATE 1

</pre>
<p>PSQL2: Como visto antes, nada foi percebido pela outra transação.</p>
<pre class="brush: sql;">

exercicio=# select * from aluno;
id |   nome
---+-----------
1 | Huguinho
2 | Zezinho
3 | Luisinho
4 | Juquinha
5 | Joaozinho
(5 rows)

</pre>
<p>PSQL1: Confirmamos a alteração</p>
<pre class="brush: sql;">

exercicio=# COMMIT ;
COMMIT

</pre>
<p>PSQL2: Agora a alteração foi sentida</p>
<pre class="brush: sql;">

exercicio=# select * from aluno;
id |     nome
---+---------------
1 | Huguinho
3 | Luisinho
4 | Juquinha
5 | Joaozinho
2 | Jose da Silva
(5 rows)

</pre>
<h3>Phantom Read</h3>
<p>O problema das leituras fantasmas é parecido com o das leituras não repetíveis, o que muda é que uma das transações em vez de alterar o conjunto de dados, insere um dado novo.</p>
<p>Vamos refazer a dinâmica. Primeiramente, encerre as transações das duas instâncias do psql. Neste momento, tanto faz encerrar com COMMIT ou com ROLLBACK. Se a transação não estiver em curso, você receberá uma mensagem como esta:</p>
<pre>WARNING:  there is no transaction in progress</pre>
<p>PSQL1: Inicie a transação e insira duas novas alunas</p>
<pre class="brush: sql;">

# begin;
BEGIN
exercicio=# INSERT INTO aluno (nome) values ('Chiquinha'), ('Mariazinha');
INSERT 0 2

</pre>
<p>PSQL2: Inicie a transação e consulte a lista de alunos</p>
<pre class="brush: sql;">

# begin;
BEGIN
exercicio=# select * from aluno;
id |     nome
----+---------------
1 | Huguinho
3 | Luisinho
4 | Juquinha
5 | Joaozinho
2 | Jose da Silva
(5 rows)

</pre>
<p>Ninguém novo até agora.</p>
<p>PSQL1: Confirme sua transação com um COMMIT</p>
<pre class="brush: sql;">

exercicio=# COMMIT ;
COMMIT

</pre>
<p>PSQL2: Veja que as novas alunas agora estão presentes</p>
<pre class="brush: sql;">

exercicio=# select * from aluno;
id |     nome
----+---------------
1 | Huguinho
3 | Luisinho
4 | Juquinha
5 | Joaozinho
2 | Jose da Silva
6 | Chiquinha
7 | Mariazinha
(7 rows)

</pre>
<h2>Níveis de isolamento</h2>
<p>Existem quatro níveis de isolamento segundo o padrão SQL:</p>
<h3>READ UNCOMMITED</h3>
<p>Nível menos isolado. Aqui, todos os problemas citados podem ocorrer, inclusive as porcas dirty reads. É muito difícil um SGBD permitir que o DBA mesmo que deliberadamente configure o nível de isolamento para READ UNCOMMITED. Em resumo, é um nível acadêmico, apenas para você dizer que conhece.</p>
<h3>READ COMMITED</h3>
<p>Nível padrão do PostgreSQL. Aqui dirty reads estão barradas, mas, como verificamos nos exercícios anteriores, unrepeatable reads e phantom reads podem ocorrer.</p>
<h3>REPEATABLE READ</h3>
<p>Neste nível, apenas as phantom reads podem ocorrer. Os SGBDs fazem bloqueio (lock) do conjunto de dados lidos em uma transação, o que evita que as leituras posteriores apresentem resultados modificados ou deletados confirmados por outra transação. No entanto, não há como fazer lock de um registro que ainda não existia na primeira leitura. Este é o motivo pelo qual as leituras fantasmas podem assombrar por aqui.</p>
<h3>SERIALIZABLE</h3>
<p>Esse é o nível que leva o isolamento ao máximo. Aqui, nenhum desses problemas podem ocorrer.</p>
<h3>Em resumo:</h3>
<p>A tabela abaixo mostra os níveis de isolamento em relação aos problemas que podem ou não provocar.</p>
<table class="CALSTABLE" border="1">
<col></col>
<col></col>
<col></col>
<col></col>
<thead>
<tr>
<th>Nível de isolamento</th>
<th>Dirty Read</th>
<th>Nonrepeatable Read</th>
<th>Phantom Read</th>
</tr>
</thead>
<tbody>
<tr>
<td>Read uncommitted</td>
<td>Possível</td>
<td>Possível</td>
<td>Possível</td>
</tr>
<tr>
<td>Read committed</td>
<td>Impossível</td>
<td>Possível</td>
<td>Possível</td>
</tr>
<tr>
<td>Repeatable read</td>
<td>Impossível</td>
<td>Impossível</td>
<td>Possível</td>
</tr>
<tr>
<td>Serializable</td>
<td>Impossível</td>
<td>Impossível</td>
<td>Impossível</td>
</tr>
</tbody>
</table>
<p>Para modificar o nível de isolamento da transação corrente, existe o comando SET TRANSACTION que deve ser executado logo após o BEGIN e antes de qualquer consulta SELECT, INSERT, DELETE, UPDATE, FETCH ou COPY. Este comando possui o seguinte formato:</p>
<p>SET TRANSACTION [ISOLATION LEVEL { SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED } ]  [ {READ WRITE | READ ONLY} ]</p>
<p>Com ele, é possível determinar o nível de isolamento, escrevendo ISOLATION LEVEL seguido por algum dos níveis já apresentados e/ou informar as opções READ WRITE (padrão) ou READ ONLY. Transações READ ONLY (somente leitura) não podem alterar nenhuma tabela que não seja temporária.</p>
<p>Vamos a um exemplo:</p>
<pre class="brush: sql;">

exercicio=# begin;
BEGIN
exercicio=# set transaction read only;
SET
exercicio=# UPDATE aluno SET nome = 'teste';
ERROR:  cannot execute UPDATE in a read-only transaction
exercicio=# select * from aluno;
ERROR:  current transaction is aborted, commands ignored until end of transaction block
exercicio=# rollback;
ROLLBACK

</pre>
<p>Após o BEGIN, determinamos que nossa transação seria somente leitura. Logo após, tentamos infringir o determinado em SET TRANSACTION executando um UPDATE qualquer. Confira o erro resultante. Segui adiante para mostrar algo que ainda não havia falado, mas ocorre sempre que uma instrução retorna erro dentro de uma transação. Qualquer nova instrução retorna o erro informando que a transação foi abortada. Cabe-nos apenas executar o ROLLBACK para encerrar a transação problemática.</p>
<p>O nível de isolamento REPEATABLE READ no PostgreSQL é encarado como um  SERIALIZABLE, ou seja, não permite leituras fantasmas. Vejamos:</p>
<p>PSQL1: Determinando o nível de isolamento para REPEATABLE READ, que no PostgreSQL funciona como um SERIALIZABLE</p>
<pre class="brush: sql;">

exercicio=# BEGIN ;
BEGIN
exercicio=# SET transaction ISOLATION LEVEL REPEATABLE READ ;
SET
exercicio=# select * from aluno;
 id |     nome
----+---------------
 1 | Huguinho
 3 | Luisinho
 4 | Juquinha
 5 | Joaozinho
 2 | Jose da Silva
 6 | Chiquinha
 7 | Mariazinha
(7 rows)

</pre>
<p>PSQL2: Inserindo um novo aluno e confirmando a transação</p>
<pre class="brush: sql;">

exercicio=# BEGIN ;
BEGIN
exercicio=# INSERT INTO aluno (nome) values ('Maradona');
INSERT 0 1
exercicio=# COMMIT ;

</pre>
<p>PSQL1: Olha aí que Maradona ainda não foi inserido e não é porque ele é argentino. Após a confirmação deste transação, ele vai aparecer. Só espero que não estrague a festa.</p>
<pre class="brush: sql;">

exercicio=# select * from aluno;
 id |     nome
----+---------------
 1 | Huguinho
 3 | Luisinho
 4 | Juquinha
 5 | Joaozinho
 2 | Jose da Silva
 6 | Chiquinha
 7 | Mariazinha
(7 rows)

exercicio=# COMMIT ;
COMMIT
exercicio=# select * from aluno;
 id |     nome
----+---------------
 1 | Huguinho
 3 | Luisinho
 4 | Juquinha
 5 | Joaozinho
 2 | Jose da Silva
 6 | Chiquinha
 7 | Mariazinha
 8 | Maradona
(8 rows)

</pre>
<p>O nível de isolamento READ UNCOMMITED é encarado como um READ COMMITED (o padão). Assim, leituras sujas jamais são permitidas, no PostgreSQL.</p>
<h2>Controle de versão</h2>
<p>Ao contrário de alguns bancos de dados, o PostgreSQL por padrão trabalha com controle de versão ao invés de bloqueio de dados na manipulação de transações. Este mecanismo, conhecido como MVCC (multi-version concurrency control) costuma ser bem mais eficiente em termos de desempenho em comparação aos bloqueios de registros que já são bem melhores do que os bloqueios de tabelas. Além do PostgreSQL, bancos como Oracle, Firebird, HiperSonic, SyBase, ZODB, entre outros também se baseiam no MVCC para controle de isolamento de transações.</p>
<p>Para cada registro de cada tabela sempre existem dois campos utilizados  no controle de transações. O campo xmin informa o id (auto-sequenciado)  da transação que gerou este registro. Já xmax determina o id da  transação que a deletou ou alterou seu conteúdo, gerando um novo  registro com xmin atualizado. Para ilustrar como funciona o controle, vejamos o exemplo:</p>
<p>PSQL1:</p>
<pre class="brush: sql;">

exercicio=# BEGIN ;
BEGIN
exercicio=# select *, xmin, xmax from aluno;
id |     nome      | xmin | xmax
---+---------------+------+------
 1 | Huguinho      |  664 |    0
 3 | Luisinho      |  664 |    0
 4 | Juquinha      |  664 |    0
 5 | Joaozinho     |  664 |    0
 2 | Jose da Silva |  668 |    0
 6 | Chiquinha     |  669 |    0
 7 | Mariazinha    |  669 |    0
 8 | Maradona      |  670 |    0
(8 rows)

exercicio=# DELETE FROM aluno WHERE nome = 'Maradona';
DELETE 1
exercicio=# select *, xmin, xmax from aluno;
 id |     nome      | xmin | xmax
----+---------------+------+------
 1 | Huguinho      |  664 |    0
 3 | Luisinho      |  664 |    0
 4 | Juquinha      |  664 |    0
 5 | Joaozinho     |  664 |    0
 2 | Jose da Silva |  668 |    0
 6 | Chiquinha     |  669 |    0
 7 | Mariazinha    |  669 |    0
(7 rows)
exercicio=# UPDATE aluno SET nome = 'Zezinho' where id = 2;
UPDATE 1
exercicio=# select *, xmin, xmax from aluno;
 id |    nome    | xmin | xmax
----+------------+------+------
 1 | Huguinho   |  664 |    0
 3 | Luisinho   |  664 |    0
 4 | Juquinha   |  664 |    0
 5 | Joaozinho  |  664 |    0
 6 | Chiquinha  |  669 |    0
 7 | Mariazinha |  669 |    0
 2 | Zezinho    |  671 |    0
(7 rows)
</pre>
<p>No primeiro SELECT, podemos visualizar o xmin de cada registro. Perceba que Jose da Silva possui um xmin maior do que os outros alunos que foram inseridos no mesmo momento. Isto se deve aos UPDATEs que executamos nos exemplos posteriores ao primeiro INSERT.</p>
<p>Realizamos um DELETE para excluir Maradona de onde ele não foi chamado. O SELECT posterior não tem como vê-lo uma vez que o xmax é justamente o id da transação atual. Após um UPDATE para voltarmos com o nome Zezinho do aluno 2, e eis que surgiu um registro no fim da fila com id de transação maior do que todos. O registro com Jose da Silva também não está mais visível aqui.</p>
<p>Agora vamos a uma outra transação antes de confirmação as alterações na atual.</p>
<p>PSQL2: Selecionando os registros em outra transação antes do COMMIT da anterior</p>
<pre class="brush: sql;">

exercicio=# SELECT *, xmin, xmax FROM aluno;
 id |     nome      | xmin | xmax
----+---------------+------+------
 1 | Huguinho      |  664 |    0
 3 | Luisinho      |  664 |    0
 4 | Juquinha      |  664 |    0
 5 | Joaozinho     |  664 |    0
 2 | Jose da Silva |  668 |  671
 6 | Chiquinha     |  669 |    0
 7 | Mariazinha    |  669 |    0
 8 | Maradona      |  670 |  671
(8 rows)

</pre>
<p>Nela, é possível visualizar os xmax de Jose da Silva e Maradona com o valor da transação em PSQL1. Como a primeira ainda não confirmou a transação, ainda não estamos vendo os novos valores aqui (só veríamos se o nível de isolamento fosse READ UNCOMMITED, que permite dirty reads e não é suportado pelo PostgreSQL).</p>
<p>Vamos confirmar e visualizar o resultado final:</p>
<p>PSQL1: Confirmação e visualização do resultado</p>
<pre class="brush: sql;">

exercicio=# COMMIT ;
COMMIT
exercicio=# select *, xmin, xmax from aluno;
 id |    nome    | xmin | xmax
----+------------+------+------
 1 | Huguinho   |  664 |    0
 3 | Luisinho   |  664 |    0
 4 | Juquinha   |  664 |    0
 5 | Joaozinho  |  664 |    0
 6 | Chiquinha  |  669 |    0
 7 | Mariazinha |  669 |    0
 2 | Zezinho    |  671 |    0
(7 rows)

</pre>
<p>PSQL2: Visualização atualizada. Se estivesse no nível de isolamento SERIALIZABLE ou REPEATABLE READ, não seria possível ver os registros atualizados e deletados.</p>
<pre class="brush: sql;">

exercicio=# SELECT *, xmin, xmax FROM aluno;
id |    nome    | xmin | xmax
----+------------+------+------
1 | Huguinho   |  664 |    0
3 | Luisinho   |  664 |    0
4 | Juquinha   |  664 |    0
5 | Joaozinho  |  664 |    0
6 | Chiquinha  |  669 |    0
7 | Mariazinha |  669 |    0
2 | Zezinho    |  671 |    0
(7 rows)

</pre>
<h2>MVCC e a necessidade de Vacuum</h2>
<p>O MVCC traz uma série de benefícios sobre os bloqueios:</p>
<ul>
<li>Processos de leitura não bloqueiam processos de escrita e vice-versa</li>
<li>Praticamente elimina a incidência de contenções de transações</li>
<li>Reduz drasticamente o risco de DEAD LOCK</li>
<li>Possui um desempenho bem superior</li>
</ul>
<p>Mas também é possível detectar um ponto fraco no MVCC. Os registros alterados e excluídos não ficam mais acessíveis mas não são imediatamente riscados do mapa. Ou seja, continuam inchando o disco. Para evitar que o banco tome proporções além do aceitável (este aceitável é bem relativo), execute regularmente (este regularmente é mais relativo ainda) a instrução VACUUM, que além de apagar os registros (VACUUM FULL) ou simplesmente apontando nas páginas de dados como área disponível para ser sobrescrita (VACUUM simples), também atualiza o ID das transações para evitar estouro e consequente reinício automático dos IDs de transações, o que poderia proporcionar resultados catrastróficos (como uma espécie de bug do milênio).</p>
<p>Como os IDs das transações são inteiros de 32 bits, a cada 4 bilhões lá vai fumaça de transações, isto poderá ocorrer. Voltaremos a este assunto em uma seção específica para o comando VACUUM, nos policiando para não realizar mais de 4 bilhões de exemplos daqui até lá.</p>
<h2>Bloqueios e deadlocks</h2>
<p>Provavelmente, você não irá precisar trabalhar com bloqueios explícitos no PostgreSQL, mas existem situações onde podem ser úteis. A prova é que o PostgreSQL implicitamente trabalha com bloqueios em algumas instruções. Pra variar, vamos a um exemplo:</p>
<p>PSQL1: Atualizando o nome de Mariazinha</p>
<pre class="brush: sql;">

exercicio=# BEGIN ;
BEGIN
exercicio=# UPDATE aluno SET nome = 'Maria Francisca' where id = 7;
UPDATE 1

</pre>
<p>PSQL2: Atualizando os nomes de Chiquinha e Mariazinha em outra transação</p>
<pre class="brush: sql;">

exercicio=# BEGIN ;
BEGIN
exercicio=# UPDATE aluno SET nome = 'Francisca' where id = 6;
UPDATE 1

exercicio=# UPDATE aluno SET nome = 'Maria Joaquina' where id = 7;

</pre>
<p>Até o primeiro UPDATE, fora o dilúvio que se abateu sobre Recife e o temporal do lado de fora da janela, estava tudo normal. No segundo, a chuva até que diminuiu, mas a alteração do nome de Mariazinha ficou congelada (bloqueada) até que o PSQL1 confirme a sua alteração. Caso não ficasse, a transação que encerrasse primeiro teria suas  alterações perdidas devido a outra que encerrasse depois, independentemente de quem realizou o UPDATE primeiro. Isto  resultaria em um outro problema de isolamento conhecido por Lost  Update, que pode ainda ser mais grave se uma transação precisar dos dados da outra para atualizar os seus, como eles não estarão disponíveis até o COMMIT, o estado final potencialmente deverá ferir o princípio da consistência.</p>
<p>Agora vamos voltar à primeira transação não para confirmar a alteração e liberar o UPDATE na segunda, mas para pôr mais lenha na fogueira e também esperar pela segunda devido a alteração do nome de Chiquinha.</p>
<p>PSQL1: Atualizando um registro já modificado pela outra transação</p>
<pre class="brush: sql;">

exercicio=# UPDATE aluno SET nome = 'Chica' where id = 6;

ERROR:  deadlock detected
DETAIL:  Process 9019 waits for ShareLock on transaction 674; blocked by process 8919.
Process 8919 waits for ShareLock on transaction 676; blocked by process 9019.
Process 9019: UPDATE aluno SET nome = 'Maria Joaquina' where id = 7;
Process 8919: UPDATE aluno SET nome = 'Chica' where id = 6;
HINT:  See server log for query details.

</pre>
<p>Booom, DEAD LOCK. Game over. Façamos um breve parênteses para a mensagem de erro do PostgreSQL que não é uma simples mensagem, é uma aula sobre o assunto. Como sei que você é uma pessoa bastante atenta, posso apostar que percebeu o termo ShareLock da mensagem de erro acima. Ela significa bloqueio compartilhado.</p>
<p>Antes de seguir adiante, façamos mais uma dinâmica a fim de colher informações do PostgreSQL acerca dos bloqueios realizados implícita ou explicitamente em determinado momento. A view de catálogo pg_locks recupera os locks gerenciados no momento pelo pelo servidor. Unindo ela com as tabelas de catálogo pg_class e pg_database podemos ter uma informação bem interessante:</p>
<p>PSQL1: Iniciando uma inofensiva transação para mais uma vez alterar o nome de Chiquinha e consultando o bloqueio gerado</p>
<pre class="brush: sql;">

exercicio=# BEGIN ;
BEGIN
exercicio=# UPDATE aluno SET nome = 'Chica' where id = 6;
UPDATE 1

exercicio=# select l.locktype, l.mode, l.pid, d.datname, c.relname from pg_locks l inner join pg_database d on l.database = d.oid inner join pg_class c on l.relation = c.oid where c.relkind = 'r' and relname not like 'pg_%';
locktype |       mode       | pid  |  datname  | relname
----------+------------------+------+-----------+---------
relation | RowExclusiveLock | 8919 | exercicio | aluno
(1 row)

</pre>
<p>O PostgreSQL reconhece oito tipos de bloqueios: ACCESS SHARE, ROW SHARE, ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE, ACCESS EXCLUSIVE.</p>
<p>É possível solicitar explicitamente um bloqueio sobre tabelas ou mesmo registros através do comando Lock:</p>
<pre class="brush: sql;">

exercicio=# \h LOCK
Command:     LOCK
Description: lock a table
Syntax:
LOCK [ TABLE ] [ ONLY ] name [, ...] [ IN lockmode MODE ] [ NOWAIT ]

where lockmode is one of:

ACCESS SHARE | ROW SHARE | ROW EXCLUSIVE | SHARE UPDATE EXCLUSIVE
| SHARE | SHARE ROW EXCLUSIVE | EXCLUSIVE | ACCESS EXCLUSIVE

</pre>
<h3>Access Share</h3>
<p>Bloqueio requerido apenas por instruções SELECT e ANALYZE são simples leitura em geral permitidas, a menos que alguma outra transação tenha requerido Access Exclusive.</p>
<h3>Row Share</h3>
<p>A instrução SELECT com a cláusula FOR UPDATE obtém este nível de bloqueio. Ele conflita apenas com os níveis mais retritos Exclusive e Access Exclusive.</p>
<h3>Row Exclusive</h3>
<p>Bloqueio solicitado automaticamente por instruções INSERT, UPDATE, DELETE. Conflita com quem precisar além de Access Exclusive e Exclusive, também com Share Row Exclusive e Share.</p>
<h3>Share Update Exclusive</h3>
<p>Bloqueio padrão do comando VACUUM sem a opção FULL. Conflita com todos os próximos níveis de bloqueio e com ele próprio. Bloqueia a tabela inteira.</p>
<h3>Share</h3>
<p>A partir deste nível de bloqueio, já vimos com quem cada um conflita. Bloqueia toda a tabela e é requerido por instruções CREATE INDEX.</p>
<h3>Share Row Exclusive</h3>
<p>Similar ao Exclusive, mas apenas no nível de linha. Não há instrução SQL que automaticamente solicite este nível de bloqueio.</p>
<h3>Exclusive</h3>
<p>Este só não conflita com Access Share. É requerido automaticamente apenas em tabelas de catálogo em determinadas operações.</p>
<h3>Access Exclusive</h3>
<p>Conflita com todo mundo. Ou seja, nenhuma outra transação pode operar na mesma tabela bloqueada por alguma transação que solicitou Access Exclusive. Instruções ALTER TABLE, DROP TABLE, TRUNCATE, REINDEX, CLUSTER e VACUUM FULL necessitam deste modo de bloqueio.</p>
<h2>Conclusão</h2>
<p>Percebeu como é denso e fundamental o assunto <em>transações</em>? Elas são provavelmente o mais frequente motivo para desenvolvedores optarem por trabalhar com SGBDs em vez de persistirem dados diretamente em arquivos.</p>
<p>Acredito que tanto DBAs quanto desenvolvedores precisam conhecer bem como o SGBD escolhido lida com elas e optar pelo PostgreSQL é optar pelo que há de mais avançado no mercado sobre o assunto.</p>
<div id="_mcePaste" style="position: absolute; left: -10000px; top: 9880px; width: 1px; height: 1px; overflow: hidden;">
<p>Prometi aos alunos da turma de PostgreSQL Developer do meu amigo  Aercio que iria de tratar aqui do modelo de transações implementado no  PostgreSQL. Como resolvi atualizar o material sobre PostgreSQL que venho  escrevendo para a versão 9.0, decidi já testar os exemplos aqui no 9.0  beta 2 recém disponibilizado para download <a href="http://wwwmaster.postgresql.org/download/mirrors-ftp/source/v9.0beta2/postgresql-9.0beta2.tar.bz2" target="_blank">no site oficial</a>. Para uma ambientação inicial no  PostgreSQL, não deixe de ler o primeiro capítulo do provável livro  (ainda não atualizado) <a href="http://especializa.com.br/blog/2010/05/18/material-de-postgresql-administrator/" target="_blank">disponível por aqui</a>.</p>
<h2><!--more-->O que são  transações?</h2>
<p>Transações são conjuntos de instruções enviadas ao banco de dados que  devem ser tratadas com uma única operação. Ou o servidor realiza tudo  ou não realiza nada. O exemplo clássico usado em 110% dos cursos de  bancos de dados é a famosa transação financeira de transferência de  fundos. Imagine que Mônica deseje transferir 100,00 reais para a conta  de Cebolinha. Esta operação na realidade se divide em duas. Um débito na  conta de Mônica e um crédito de mesmo valor  na conta de Cebolinha (a  ordem não interfere). Agora, se logo após o débito na primeira conta, o  servidor sair do ar antes de que possa executar a segunda. Para onde  foram os 100,00 reais?</p>
<p>Existem quatro características que os SGBDs devem garantir se  pretendem lidar com transações com segurança. O conjunto dessas  características é conhecido como ACID (atomicity, consistency,  isolation, durability). Vejamos o que significam:</p>
<h3>Atomicidade</h3>
<p>É o ponto chave. É quem determina o tudo-ou-nada. A comunidade  científica por longos anos acreditou que um átomo seria a unidade mínima  da matéria, sólido e indivisível. Já se sabe em simples aulas de  química que essa idéia caiu há séculos. No entanto, este conceito serve  de analogia para ocasiões onde deve se considerar a impossibilidade de  dividir algo em unidades menores.</p>
<p>Sendo assim, a atomicidade é a característica que torna um conjunto  de operações um agrupamento indivisível executado por completo ou nada  feito.</p>
<h3>Consistência</h3>
<p>Esta característica determina que o SGBD deve iniciar uma transação  em um estado consistente e terminá-la deixando os dados em outro estado  consistente. Caso alguma ação dentro de uma transação dê problemas e  fira alguma regra de consistência de dados (como uma falha de escrita em  disco, por exemplo), toda a transação deve ser completamente abortada,  deixando os dados em seu estado consistente imediatamente anterior. O  ato de abortar e voltar ao ponto inicial é conhecido como ROLLBACK.</p>
<p>Inconsistências não se resumem a problemas físicos de dados. É  possível encarar uma situação fisicamente consistente como logicamente  inconsistente. No exemplo dos desaparecimento dos 100,00 reais, o banco  ficou em um estado inconsistente uma vez que se somarmos os saldos nas  duas contas, independentemente do valor transferido os resultados pré e  pós transferência deveria dar consistentemente o mesmo.</p>
<h3>Isolamento</h3>
<p>Esta característica remete a acessos concorrentes. Caso apenas uma  transação seja executada no SGBD em determinado intervalo de tempo, o  isolamento não vai lhe acrescentar nada. No entanto, como sabemos que  SGBDs, na maioria dos casos, são usados por sistemas concorrentes, esta  característica entra para garantir que informações de uma transação não  se misturem com de outras.</p>
<p>Esta é a única característica relativa das quatro. Ou seja, é a única  que não necessariamente é ruim quando infringida de alguma forma. Por  um lado, seria ruim dados de uma transação estarem visíveis a outras  como no problema clássico conhecido como Dirty Read que ocorre quando  uma transação tx1 modifica alguns registros, uma transação tx2 ao  selecionar alguns dados, visualiza esses registros alterados por tx1,  mas esta primeira é abortada por alguma razão. Em resumo, tx2 leu dados  errados, uma vez que eles nunca deveriam ter sido alterados.</p>
<p>Por outro lado, a capacidade de trabalhar com múltiplas transações ao  mesmo tempo pode garantir o bom desempenho em sistemas muito acessados,  uma vez que uma não fica esperando pela conclusão da outra. Elas são  implementadas em ambientes multi-processos ou multi-threads e podem  explorar melhor o pontencial multi-core dos hardwares atuais.</p>
<h3>Durabilidade</h3>
<p>Toda transação é encerrada com a execução da ação COMMIT. Esta  característica é a garantia dada pelo SGBD que quando retornar um sinal  COMMIT, os dados já devem estar gravados no meio persistente e ele deve  ser capaz de recuperar esses dados em transações posteriores. Caso haja  algum problema, a transação deve ser abortada. Conforme a regra da  consistência, caso não haja problemas, o banco está jurando de pés  juntos que gravou e você vai poder ler depois.</p>
<p>Como tratado no capítulo 1, o PostgreSQL escreve em disco registros  de log WAL e assim que estejam completamente escritos, retorna o sinal  COMMIT. Já no capítulo 2, tratamos das diretivas do postgresql.conf <em>fsync</em> e <em>wal_sync_method</em>. Para recapitular, a primeira faz com que o  SGBD após a execução do comando de escrita do segmento WAL realize  consultas ao sistema operacional a fim de obter a garantia de que o  valor escrito está completamente descarregado no disco. Já a segunda  determina o método adotado nesta pesquisa. Desligar a diretiva fsync  pode proporcionar um maior desempenho, mas o risco da falta de  durabilidade de uma transação &#8220;commitada&#8221; em geral não faz valer a pena  esse tuning desesperado.</p>
<p>O método de sincronização com o disco de segmentos WAL (<em>wal_sync_method</em>)  padrão do Linux é o <em>fdatasync</em>. O método <em>fsync</em> aguarda o  sistema operacional descarregar em disco toda a pilha do buffer de I/O e  atualizar o inode do sistema de arquivos com a informação de última  alteração do arquivo escrito. O <em>fdatasync</em> não espera pela  atualização deste metadado. Ele se preocupa apenas com o que mais  importa que é a atualização propriamente dita dos dados.</p>
<h2>Transações e Write-ahead Logging</h2>
<p>Ao confirmar cada transação, o servidor não registra em disco as  páginas de dados (aqueles 8Kb que ficam na memória). É bem menos custoso  registrar neste momento (e esperar pelo fsync), um registro de segmento  WAL uma vez que ele é linear (alterações na base real podem envolver  dados que não necessariamente estejam na mesma página de dados) e por  isso realizam movimentos menores da controladora de disco, além de não  requerer pesquisa por espaço livre (FSM).</p>
<p>Por outro lado, a manipulação das páginas de memória compartilhada  que são carregadas do e para o disco é bem mais vantajoso do que sair  vasculhando registros de log de operações a fim de obter os dados  necessários nas consultas.</p>
<p>Sendo assim, o PostgreSQL precisa realizar essa tradução do que é  salvo em log para as páginas de dados de tempos em tempos. Este processo  de escrita em background (bgwriter) é denominado CHECKPOINT. No  PostgreSQL, checkpoints não têm ligação direta com o COMMIT de uma  transação, mas achei interessante ilustrar o que deve ocorrer com  algumas transações confirmadas entre um checkpoint e um provável crash  da base.</p>
<p>O vídeo abaixo mostra o que ocorre com 5 transações considerando que  em um momento distinto haverá uma ação de checkpoint e posteriormente  haverá um crash do servidor, provocando sua queda inesperada. Caso você  não consiga ver o vídeo, clique para baixá-lo em <a href="http://www.especializa.com.br/blog/wp-content/uploads/2010/06/transacoes.ogv">Ogv</a> (theora) ou <a href="http://www.especializa.com.br/blog/wp-content/uploads/2010/06/transacoes.mp4">MP4</a> (H.264).</p>
<p>Seu browser não suporta vídeos HTML5. Operamor de Deus, chreima ele  no firefoxgo.</p>
<h2>Problemas de isolamento</h2>
<p>Transações já são interessantes mesmo em cenário standalone, uma vez  que é possível desfazer todo um bloco de procedimentos apenas com um  simples Rollback. Mas é em sistemas concorrentes que o bicho pega. Caso o  isolamento não seja garantido alguns problemas podem ocorrer.</p>
<h3>Dirty read</h3>
<p>O primeiro deles já foi apontado. Dirty Read ou leitura suja acontece  quando uma transação lê algum dado alterado por outra que acaba não  confirmando suas alterações. É um problema extremo que de fato não é  comum que bancos de dados permitam que ocorra. O PostgreSQL não permite.  Confira no exercício abaixo. Primeiramente, vamos criar a base para os  testes:</p>
<pre class="brush: sql;">

postgres=# CREATE DATABASE exercicio;
CREATE DATABASE
postgres=# \c exercicio
You are now connected to database &quot;exercicio&quot;.
exercicio=# create table aluno (id serial primary key, nome varchar(50));
NOTICE:  CREATE TABLE will create implicit sequence &quot;aluno_id_seq&quot; for serial column &quot;aluno.id&quot;
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index &quot;aluno_pkey&quot; for table &quot;aluno&quot;
CREATE TABLE
exercicio=# INSERT into aluno (nome) values ('Huguinho'), ('Zezinho'), ('Luisinho'), ('Juquinha'), ('Joaozinho');
INSERT 0 5
</pre>
<p>Criamos 5 alunos (só peça boa =) em uma nova base. Agora vamos ao  primeiro cenário. Para isto será necessário que você abra duas  instâncias do psql ou qualquer outra ferramenta cliente (que certamente  não será tão limpa e cheirosa quanto o psql).</p>
<p>Inicie uma transação em cada uma executando em ambas a instrução:</p>
<pre class="brush: sql;">

exercicio=# begin;
BEGIN

</pre>
<p>Na primeira, execute o seguinte comando:</p>
<pre class="brush: sql;">

exercicio=# UPDATE aluno set nome = 'Jose da Silva' where id = 2;
UPDATE 1

exercicio=# select * from aluno;
id |     nome
----+---------------
1 | Huguinho
3 | Luisinho
4 | Juquinha
5 | Joaozinho
2 | Jose da Silva
(5 rows)

</pre>
<p>Veja que o nome de Zezinho agora é Jose da Silva.</p>
<p>Realize a mesma consulta na segunda instância do psql:</p>
<pre class="brush: sql;">

exercicio=# select * from aluno;
id |   nome
----+-----------
1 | Huguinho
2 | Zezinho
3 | Luisinho
4 | Juquinha
5 | Joaozinho
(5 rows)

</pre>
<p>Como você atentou, os alunos continuam com seus nomes intactos. O que  a primeira transação modificou não foi sentido pela segunda. O motivo é  que se a primeira voltar atrás, a segunda já teria lido e tomado suas  próprias decisões baseando-se em dados inconsistentes. Confira como  faríamos para reverter (execute na primeira transação):</p>
<pre class="brush: sql;">

exercicio=# ROLLBACK ;
ROLLBACK
exercicio=# select * from aluno;
id |   nome
----+-----------
1 | Huguinho
2 | Zezinho
3 | Luisinho
4 | Juquinha
5 | Joaozinho
(5 rows)

</pre>
<p>Nossa base voltou ao estágio em que estava no momento do BEGIN.</p>
<h3>Unrepeatable Reads</h3>
<p>Leituras não repetíveis acontecem quando uma transação lê algum dado  em determinado momento, posteriormente outra transação altera ou deleta e  executa o COMMIT. A primeira volta a ler os mesmos dados e constata que  eles não estão mais como antes. Este não chega a ser um problema em  muitos sistemas, a ponto do PostgreSQL, em seu nível de isolamento  padrão, permitir que ocorra. Vejamos:</p>
<p>PSQL1: Iniciamos uma nova transação (aquela anterior foi desfeita com  ROLLBACK) e atualizamos novamente o nome de Zezinho.</p>
<pre class="brush: sql;">

exercicio=# BEGIN ;
BEGIN
exercicio=# UPDATE aluno set nome = 'Jose da Silva' where id = 2;
UPDATE 1

</pre>
<p>PSQL2: Como visto antes, nada foi percebido pela outra transação.</p>
<pre class="brush: sql;">

exercicio=# select * from aluno;
id |   nome
---+-----------
1 | Huguinho
2 | Zezinho
3 | Luisinho
4 | Juquinha
5 | Joaozinho
(5 rows)

</pre>
<p>PSQL1: Confirmamos a alteração</p>
<pre class="brush: sql;">

exercicio=# COMMIT ;
COMMIT

</pre>
<p>PSQL2: Agora a alteração foi sentida</p>
<pre class="brush: sql;">

exercicio=# select * from aluno;
id |     nome
---+---------------
1 | Huguinho
3 | Luisinho
4 | Juquinha
5 | Joaozinho
2 | Jose da Silva
(5 rows)

</pre>
<h3>Phantom Read</h3>
<p>O problema das leituras fantasmas é parecido com o das leituras não  repetíveis, o que muda é que uma das transações em vez de alterar o  conjunto de dados, insere um dado novo.</p>
<p>Vamos refazer a dinâmica. Primeiramente, encerre as transações das  duas instâncias do psql. Neste momento, tanto faz encerrar com COMMIT ou  com ROLLBACK. Se a transação não estiver em curso, você receberá uma  mensagem como esta:</p>
<pre>WARNING:  there is no transaction in progress</pre>
<p>PSQL1: Inicie a transação e insira duas novas alunas</p>
<pre class="brush: sql;">

# begin;
BEGIN
exercicio=# INSERT INTO aluno (nome) values ('Chiquinha'), ('Mariazinha');
INSERT 0 2

</pre>
<p>PSQL2: Inicie a transação e consulte a lista de alunos</p>
<pre class="brush: sql;">

# begin;
BEGIN
exercicio=# select * from aluno;
id |     nome
---+---------------
1 | Huguinho
3 | Luisinho
4 | Juquinha
5 | Joaozinho
2 | Jose da Silva
(5 rows)

</pre>
<p>Ninguém novo até agora.</p>
<p>PSQL1: Confirme sua transação com um COMMIT</p>
<pre class="brush: sql;">

exercicio=# COMMIT ;
COMMIT

</pre>
<p>PSQL2: Veja que as novas alunas agora estão presentes</p>
<pre class="brush: sql;">

exercicio=# select * from aluno;
id |     nome
---+---------------
1 | Huguinho
3 | Luisinho
4 | Juquinha
5 | Joaozinho
2 | Jose da Silva
6 | Chiquinha
7 | Mariazinha
(7 rows)

</pre>
<h2>Níveis de isolamento</h2>
<p>Existem quatro níveis de isolamento segundo o padrão SQL:</p>
<h3>READ UNCOMMITED</h3>
<p>Nível menos isolado. Aqui, todos os problemas citados podem ocorrer,  inclusive as porcas dirty reads. É muito difícil um SGBD permitir que o  DBA mesmo que deliberadamente configure o nível de isolamento para READ  UNCOMMITED. Em resumo, é um nível acadêmico, apenas para você dizer que  conhece.</p>
<h3>READ COMMITED</h3>
<p>Nível padrão do PostgreSQL. Aqui dirty reads estão barradas, mas,  como verificamos nos exercícios anteriores, unrepeatable reads e phantom  reads podem ocorrer.</p>
<h3>REPEATABLE READ</h3>
<p>Neste nível, apenas as phantom reads podem ocorrer. Os SGBDs fazem  bloqueio (lock) do conjunto de dados lidos em uma transação, o que evita  que as leituras posteriores apresentem resultados modificados ou  deletados confirmados por outra transação. No entanto, não há como fazer  lock de um registro que ainda não existia na primeira leitura. Este é o  motivo pelo qual as leituras fantasmas podem assombrar por aqui.</p>
<h3>SERIALIZABLE</h3>
<p>Esse é o nível que leva o isolamento ao máximo. Aqui, nenhum desses  problemas podem ocorrer.</p>
<h3>Em resumo:</h3>
<p>A tabela abaixo mostra os níveis de isolamento em relação aos  problemas que podem ou não provocar.</p>
<table class="CALSTABLE" border="1">
<col></col>
<col></col>
<col></col>
<col></col>
<thead>
<tr>
<th>Nível de isolamento</th>
<th>Dirty Read</th>
<th>Nonrepeatable Read</th>
<th>Phantom Read</th>
</tr>
</thead>
<tbody>
<tr>
<td>Read uncommitted</td>
<td>Possível</td>
<td>Possível</td>
<td>Possível</td>
</tr>
<tr>
<td>Read committed</td>
<td>Impossível</td>
<td>Possível</td>
<td>Possível</td>
</tr>
<tr>
<td>Repeatable read</td>
<td>Impossível</td>
<td>Impossível</td>
<td>Possível</td>
</tr>
<tr>
<td>Serializable</td>
<td>Impossível</td>
<td>Impossível</td>
<td>Impossível</td>
</tr>
</tbody>
</table>
<p>Para modificar o nível de isolamento da transação corrente, existe o  comando SET TRANSACTION que deve ser executado logo após o BEGIN e antes  de qualquer consulta SELECT, INSERT, DELETE, UPDATE, FETCH ou COPY.  Este comando possui o seguinte formato:</p>
<p>SET TRANSACTION [ISOLATION LEVEL { SERIALIZABLE | REPEATABLE READ |  READ COMMITTED | READ UNCOMMITTED } ]  [ {READ WRITE | READ ONLY} ]</p>
<p>Com ele, é possível determinar o nível de isolamento, escrevendo  ISOLATION LEVEL seguido por algum dos níveis já apresentados e/ou  informar as opções READ WRITE (padrão) ou READ ONLY. Transações READ  ONLY (somente leitura) não podem alterar nenhuma tabela que não seja  temporária.</p>
<p>Vamos a um exemplo:</p>
<pre class="brush: sql;">

exercicio=# begin;
BEGIN
exercicio=# set transaction read only;
SET
exercicio=# UPDATE aluno SET nome = 'teste';
ERROR:  cannot execute UPDATE in a read-only transaction
exercicio=# select * from aluno;
ERROR:  current transaction is aborted, commands ignored until end of transaction block
exercicio=# rollback;
ROLLBACK

</pre>
<p>Após o BEGIN, determinamos que nossa transação seria somente leitura.  Logo após, tentamos infringir o determinado em SET TRANSACTION  executando um UPDATE qualquer. Confira o erro resultante. Segui adiante  para mostrar algo que ainda não havia falado, mas ocorre sempre que uma  instrução retorna erro dentro de uma transação. Qualquer nova instrução  retorna o erro informando que a transação foi abortada. Cabe-nos apenas  executar o ROLLBACK para encerrar a transação problemática.</p>
<p>O nível de isolamento REPEATABLE READ no PostgreSQL é encarado como  um  SERIALIZABLE, ou seja, não permite leituras fantasmas. Vejamos:</p>
<p>PSQL1: Determinando o nível de isolamento para REPEATABLE READ, que  no PostgreSQL funciona como um SERIALIZABLE</p>
<pre class="brush: sql;">

exercicio=# BEGIN ;
BEGIN
exercicio=# SET transaction ISOLATION LEVEL REPEATABLE READ ;
SET
exercicio=# select * from aluno;
 id |     nome
---+---------------
 1 | Huguinho
 3 | Luisinho
 4 | Juquinha
 5 | Joaozinho
 2 | Jose da Silva
 6 | Chiquinha
 7 | Mariazinha
(7 rows)

</pre>
<p>PSQL2: Inserindo um novo aluno e confirmando a transação</p>
<pre class="brush: sql;">

exercicio=# BEGIN ;
BEGIN
exercicio=# INSERT INTO aluno (nome) values ('Maradona');
INSERT 0 1
exercicio=# COMMIT ;

</pre>
<p>PSQL1: Olha aí que Maradona ainda não foi inserido e não é porque ele  é argentino. Após a confirmação deste transação, ele vai aparecer. Só  espero que não estrague a festa.</p>
<pre class="brush: sql;">

exercicio=# select * from aluno;
 id |     nome
---+---------------
 1 | Huguinho
 3 | Luisinho
 4 | Juquinha
 5 | Joaozinho
 2 | Jose da Silva
 6 | Chiquinha
 7 | Mariazinha
(7 rows)

exercicio=# COMMIT ;
COMMIT
exercicio=# select * from aluno;
 id |     nome
---+---------------
 1 | Huguinho
 3 | Luisinho
 4 | Juquinha
 5 | Joaozinho
 2 | Jose da Silva
 6 | Chiquinha
 7 | Mariazinha
 8 | Maradona
(8 rows)

</pre>
<p>O  nível de isolamento READ UNCOMMITED é encarado como um READ COMMITED (o  padão). Assim, leituras sujas jamais são permitidas, no PostgreSQL.</p>
<h2>Bloqueios e controle de versão</h2>
<p>Ao contrário de alguns bancos de dados, o PostgreSQL por padrão  trabalha com controle de versão ao invés de bloqueio de dados na  manipulação de transações. Este mecanismo, conhecido como MVCC  (multi-version concurrency control) costuma ser bem mais eficiente em  termos de desempenho em comparação aos bloqueios de registros que já são  bem melhores do que os bloqueios de tabelas. Além do PostgreSQL, bancos  como Oracle, Firebird, HiperSonic, SyBase, ZODB, entre outros também se  baseiam no MVCC para controle de isolamento de transações.</p>
<p>Para  cada registro de cada tabela sempre existem dois campos utilizados  no  controle de transações. O campo xmin informa o id (auto-sequenciado)  da  transação que gerou este registro. Já xmax determina o id da  transação  que a deletou ou alterou seu conteúdo, gerando um novo  registro com  xmin atualizado. Para ilustrar como funciona o controle, vejamos o  exemplo:</p>
<p>PSQL1:</p>
<pre class="brush: sql;">

exercicio=# BEGIN ;
BEGIN
exercicio=# select *, xmin, xmax from aluno;
 id |     nome      | xmin | xmax
---+---------------+------+------
 1 | Huguinho      |  664 |    0
 3 | Luisinho      |  664 |    0
 4 | Juquinha      |  664 |    0
 5 | Joaozinho     |  664 |    0
 2 | Jose da Silva |  668 |    0
 6 | Chiquinha     |  669 |    0
 7 | Mariazinha    |  669 |    0
 8 | Maradona      |  670 |    0
(8 rows)

exercicio=# DELETE FROM aluno WHERE nome = 'Maradona';
DELETE 1
exercicio=# select *, xmin, xmax from aluno;
 id |     nome      | xmin | xmax
---+---------------+------+------
 1 | Huguinho      |  664 |    0
 3 | Luisinho      |  664 |    0
 4 | Juquinha      |  664 |    0
 5 | Joaozinho     |  664 |    0
 2 | Jose da Silva |  668 |    0
 6 | Chiquinha     |  669 |    0
 7 | Mariazinha    |  669 |    0
(7 rows)
exercicio=# UPDATE aluno SET nome = 'Zezinho' where id = 2;
UPDATE 1
exercicio=# select *, xmin, xmax from aluno;
 id |    nome    | xmin | xmax
---+------------+------+------
 1 | Huguinho   |  664 |    0
 3 | Luisinho   |  664 |    0
 4 | Juquinha   |  664 |    0
 5 | Joaozinho  |  664 |    0
 6 | Chiquinha  |  669 |    0
 7 | Mariazinha |  669 |    0
 2 | Zezinho    |  671 |    0
(7 rows)
</pre>
<p>No  primeiro SELECT, podemos visualizar o xmin de cada registro. Perceba  que Jose da Silva possui um xmin maior do que os outros alunos que foram  inseridos no mesmo momento. Isto se deve aos UPDATEs que executamos nos  exemplos posteriores ao primeiro INSERT.</p>
<p>Realizamos um DELETE  para excluir Maradona de onde ele não foi chamado. O SELECT posterior  não tem como vê-lo uma vez que o xmax é justamente o id da transação  atual. Após um UPDATE para voltarmos com o nome Zezinho do aluno 2, e  eis que surgiu um registro no fim da fila com id de transação maior do  que todos. O registro com Jose da Silva também não está mais visível  aqui.</p>
<p>Agora vamos a uma outra transação antes de confirmação as  alterações na atual.</p>
<p>PSQL2: Selecionando os registros em outra  transação antes do COMMIT da anterior</p>
<pre class="brush: sql;">

exercicio=#
SELECT *, xmin, xmax FROM aluno;
 id |     nome      | xmin | xmax
---+---------------+------+------
 1 | Huguinho      |  664 |    0
 3 | Luisinho      |  664 |    0
 4 | Juquinha      |  664 |    0
 5 | Joaozinho     |  664 |    0
 2 | Jose da Silva |  668 |  671
 6 | Chiquinha     |  669 |    0
 7 | Mariazinha    |  669 |    0
 8 | Maradona      |  670 |  671
(8 rows)

</pre>
<p>Nela, é possível visualizar os xmax de  Jose da Silva e Maradona com o valor da transação em PSQL1. Como a  primeira ainda não confirmou a transação, ainda não estamos vendo os  novos valores aqui (só veríamos se o nível de isolamento fosse READ  UNCOMMITED, que permite dirty reads e não é suportado pelo PostgreSQL).</p>
<p>Vamos  confirmar e visualizar o resultado final:</p>
<p>PSQL1: Confirmação e  visualização do resultado</p>
<pre class="brush: sql;">

exercicio=# COMMIT ;
COMMIT
exercicio=# select *, xmin, xmax from aluno;
 id |    nome    | xmin | xmax
---+------------+------+------
 1 | Huguinho   |  664 |    0
 3 | Luisinho   |  664 |    0
 4 | Juquinha   |  664 |    0
 5 | Joaozinho  |  664 |    0
 6 | Chiquinha  |  669 |    0
 7 | Mariazinha |  669 |    0
 2 | Zezinho    |  671 |    0
(7 rows)

</pre>
<p>PSQL2: Visualização atualizada. Se estivesse  no nível de isolamento SERIALIZABLE ou REPEATABLE READ, não seria  possível ver os registros atualizados e deletados (leituras não  repetíveis).</p>
</div>
]]></content:encoded>
			<wfw:commentRss>https://especializa.com.br/blog/2010/06/27/transacoes-no-postgresql/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
<enclosure url="http://www.especializa.com.br/blog/wp-content/uploads/2010/06/transacoes.mp4" length="4726382" type="video/mp4" />
		</item>
	</channel>
</rss>
