Você está na página 1de 8

Controle de concorrncia no PostgreSQL

O objetivo deste artigo descrever o comportamento do sistema gerenciador de banco de dados PostgreSQL, quando duas ou mais sesses tentam acessar os mesmos dados ao mesmo tempo. O objetivo nesta situao permitir o acesso eficiente para todas as sesses mantendo, ao mesmo tempo, uma rigorosa integridade dos dados. Todos os desenvolvedores de aplicao de banco de dados devem estar familiarizados com os tpicos cobertos por este captulo.

1. Introduo
Diferentemente dos sistemas gerenciadores de banco de dados tradicionais, que usam bloqueios para realizar o controle de concorrncia, o PostgreSQL mantm a consistncia dos dados utilizando o modelo multiverso (Multiversion Concurrency Control, MVCC). Isto significa que ao consultar o banco de dados, cada transao enxerga um instantneo (snapshot) dos dados (uma verso do banco de dados) conforme estes dados eram h algum tempo atrs, sem levar em considerao o estado corrente dos dados subjacentes. Este modelo impede que a transao enxergue dados inconsistentes, que poderiam ser causados por atualizaes feitas por transaes concorrentes nas mesmas linhas de dados, fornecendo um isolamento da transao para cada uma das sesses do banco de dados. A diferena principal entre os modelos multiverso e de bloqueio que, no MVCC, os bloqueios obtidos para consultar (ler) os dados no conflitam com os bloqueios obtidos para escrever os dados e, portanto, a leitura nunca bloqueia a escrita, e a escrita nunca bloqueia a leitura. As funcionalidades de bloqueio, no nvel de tabela e de linha, tambm esto disponveis no PostgreSQL para aplicaes que no podem se adaptar facilmente ao comportamento MVCC. Entretanto, a utilizao apropriada do MVCC geralmente produz um desempenho melhor que os bloqueios.

2. Isolamento da transao
O padro SQL define quatro nveis de isolamento de transao em termos de trs fenmenos que devem ser evitados entre transaes concorrentes. Os fenmenos no desejveis so: dirty read (leitura suja) A transao l dados no efetivados (uncommitted) escritos por uma transao concorrente. nonrepeatable read (leitura que no pode ser repetida) A transao l uma segunda vez os dados, e descobre que os dados foram modificados por outra transao (que os efetivou aps ter sido feita a leitura anterior). phantom read (leitura fantasma) A transao executa uma segunda vez uma consulta que retorna um conjunto de linhas que satisfaz uma determinada condio de procura, e descobre que o conjunto de linhas que satisfaz a condio diferente devido a uma outra transao efetivada recentemente.

Os quatro nveis de isolamento de transao, e seus comportamentos correspondentes, esto descritos na Tabela 9-1. Tabela 9-1. Nveis de isolamento da transao no SQL Nvel de isolamento Read uncommitted Read committed Repeatable read Serializable Dirty Read Possvel Impossvel Impossvel Impossvel Nonrepeatable Read Possvel Possvel Impossvel Impossvel Phantom Read Possvel Possvel Possvel Impossvel

O PostgreSQL disponibiliza os nveis de isolamento read committed e serializable (serializvel).

2.1. Nvel de isolamento Read Committed


O Read Committed (l efetivado) o nvel de isolamento padro do PostgreSQL. Quando uma transao processa sob este nvel de isolamento, o comando SELECT enxerga apenas os dados efetivados antes da consulta comear; nunca enxerga dados no efetivados, ou as mudanas efetivadas durante a execuo da consulta pelas transaes concorrentes (Entretanto, o SELECT enxerga os efeitos das atualizaes anteriores executadas dentro de sua prpria transao, mesmo que ainda no tenham sido efetivadas). Na verdade, o comando SELECT enxerga um instantneo do banco de dados, conforme este era no instante que a consulta comeou a executar. Observe que dois comandos SELECTs sucessivos enxergam dados diferentes, mesmo estando dentro de uma mesma transao, se outras transaes efetivarem alteraes durante a execuo do primeiro comando SELECT. Os comando UPDATE, DELETE e SELECT FOR UPDATE se comportam do mesmo modo que o SELECT para encontrar as linhas de destino: somente encontram linhas de destino efetivadas at a hora do incio do comando. Entretanto, alguma linha de destino pode ter sido atualizada (ou excluda ou marcada para atualizao) por outra transao concorrente, na hora que for encontrada. Neste caso, o atualizador (would-be updater) aguarda a transao de atualizao que comeou primeiro efetivar ou desfazer (se ainda estiver executando). Se o primeiro atualizador desfizer (rolls back), ento seus efeitos so negados e o segundo atualizador pode prosseguir com a atualizao da linha original encontrada. Se o primeiro atualizador efetivar, o segundo atualizador ignora a linha se esta foi excluda pelo primeiro atualizador, seno tenta aplicar esta operao na verso atualizada da linha. A condio de procura do comando (clusula WHERE) avaliada novamente para verificar se a verso atualizada da linha ainda corresponde condio de procura. Se corresponder, o segundo atualizador prossegue sua operao comeando a partir da verso atualizada da linha. Devido regra acima possvel os comandos de atualizao enxergarem instantneos inconsistentes --- podem enxergar os efeitos de comandos de atualizao concorrentes que afetam as mesmas linhas que esto tentando atualizar, mas no enxergam os efeitos destes comandos em outras linhas do banco de dados. Este comportamento torna o Read Committed menos apropriado para os comandos envolvendo condies de procura complexas. Entretanto, apropriado para casos mais simples. Por exemplo, considere a atualizao do saldo bancrio pela transao mostrada abaixo:
BEGIN; UPDATE conta SET saldo = saldo + 100.00 WHERE num_conta = 12345; UPDATE conta SET saldo = saldo - 100.00 WHERE num_conta = 7534; COMMIT;

Se duas transaes deste tipo tentam mudar concorrentemente o saldo da conta 12345 claro que se deseja que a segunda transao comece a partir da verso atualizada da linha da conta. Como cada comando afeta apenas uma linha predeterminada, permitir enxergar a verso atualizada da linha no cria nenhuma inconsistncia problemtica. Uma vez que no modo Read Committed cada novo comando comea com um novo instantneo, incluindo todas as transaes efetivadas at este instante, os comandos seguintes na mesma transao sempre enxergam os efeitos das transaes concorrentes efetivadas. O ponto em questo se dentro de um nico comando enxergada uma viso totalmente consistente do banco de dados. O isolamento parcial da transao fornecido pelo modo Read Committed adequado para muitas aplicaes, e este modo rpido e fcil de ser utilizado. Entretanto, para aplicaes que efetuam consultas e atualizaes complexas, pode ser necessrio garantir uma viso consistente mais rigorosa do banco de dados que a fornecida pelo modo Read Committed.

2.2. Nvel de isolamento serializvel


Serializvel o nvel mais rigoroso de isolamento da transao. Este nvel emula a execuo serial da transao, como se todas as transaes fossem executadas uma aps a outra, em srie, em vez de concorrentemente. Entretanto, as aplicaes que utilizam este nvel de isolamento devem estar preparadas para tentar novamente as transaes devido a falhas na serializao. Quando uma transao est no nvel serializvel, o comando a SELECT enxerga apenas os dados efetivados antes da transao comear; nunca enxerga dados no efetivados ou mudanas efetivadas durante a execuo da transao por transaes concorrentes (Entretanto, o comando SELECT enxerga os efeitos de atualizaes anteriores executadas dentro de sua prpria transao, mesmo que ainda no tenham sido efetivadas). Isto diferente do Read Committed, porque o comando SELECT enxerga um instantneo do incio da transao, e no do incio do comando corrente dentro da transao. Portanto, comandos SELECTs sucessivos dentro de uma mesma transao sempre enxergam os mesmos dados. Os comandos UPDATE, DELETE e SELECT FOR UPDATE se comportam do mesmo modo que o SELECT para encontrar as linhas de destino: somente encontram linhas de destino efetivadas at a hora do incio da transao. Entretanto, alguma linha de destino pode ter sido atualizada (ou excluda ou marcada para atualizao) por outra transao concorrente na hora que for encontrada. Neste caso, a transao serializvel aguarda a transao de atualizao que comeou primeiro efetivar ou desfazer as alteraes (se ainda estiver executando). Se a transao desfizer as alteraes, ento seus efeitos so negados e a transao serializvel pode prosseguir com a atualizao da linha original encontrada. Porm, se as alteraes forem efetivadas (e a linha for realmente atualizada ou excluda, e no apenas selecionada para atualizao), ento a transao serializvel desfeita com a mensagem
ERROR: Can't serialize access due to concurrent update

porque uma transao serializvel no pode modificar linhas alteradas por outra transao aps ter comeado. Quando uma aplicao recebe esta mensagem de erro, deve abortar a transao corrente e tentar executar novamente toda a transao a partir do incio. Da segunda vez em diante, a transao passa a enxergar a modificao efetivada anteriormente como parte da sua viso inicial do banco de dados e, portanto, no existir conflito lgico em usar a nova verso da linha como o ponto de partida para a atualizao na nova transao.

Observe que somente as transaes que atualizam podem precisar de novas tentativas --- as transaes apenas de leitura nunca ocasionam conflito de serializao. O modo serializvel fornece uma garantia rigorosa que cada transao enxerga apenas vises completamente consistentes do banco de dados. Entretanto, a aplicao precisa estar preparada para executar novamente as transaes quando as atualizaes concorrentes tornam impossvel sustentar a iluso de uma execuo serial. Uma vez que o custo de refazer transaes complexas pode ser significativo, este modo recomendado somente quando as transaes efetuando atualizaes contm lgica suficientemente complexa a ponto de produzir respostas erradas no modo Read Committed. Habitualmente, o modo serializvel necessrio quando a transao realiza vrias consultas sucessivas que necessitam enxergar vises idnticas do banco de dados.

3. Bloqueio explcito
O PostgreSQL fornece vrios modos de bloqueio para controlar o aceso concorrente aos dados nas tabelas. Estes modos podem ser utilizados para o bloqueio controlado pela transao, nas situaes onde o MVCC no fornece o comportamento adequado. Tambm, a maioria dos comandos do PostgreSQL obtm, automaticamente, bloqueios com modos apropriados para garantir que as tabelas referenciadas no sero excludas ou modificadas de forma incompatvel enquanto o comando executa (Por exemplo, o comando ALTER TABLE no pode executar concorrentemente com outras operaes na mesma tabela).

3.1. Bloqueios no nvel de tabela


A lista abaixo mostra os modos de bloqueio disponveis e os contextos nos quais estes modos so utilizados automaticamente pelo PostgreSQL. Lembre-se que todos estes modos de bloqueio so no nvel de tabela, mesmo que o nome contenha a palavra "row" (linha). Os nomes dos modos de bloqueio so histricos. De alguma forma os nomes refletem a utilizao tpica de cada modo de bloqueio --- mas as semnticas so todas as mesmas. A nica diferena real entre um modo de bloqueio e outro o conjunto de modos de bloqueio com o qual cada um conflita. Duas transaes no podem obter modos de bloqueio conflitantes na mesma tabela ao mesmo tempo (Entretanto, uma transao nunca conflita consigo mesma --- por exemplo, pode obter o bloqueio ACCESS EXCLUSIVE e mais tarde obter o bloqueio ACCESS SHARE na mesma tabela). Modos de bloqueio no conflitantes podem ser obtidos concorrentemente por muitas transaes. Em particular, deve ser observado que alguns modos de bloqueio so auto-conflitantes (por exemplo, o modo de bloqueio ACCESS EXCLUSIVE no pode ser obtido por mais de uma transao ao mesmo tempo), enquanto outros no so auto-conflitantes (por exemplo, o modo de bloqueio ACCESS SHARE pode ser obtido por vrias transaes). Uma vez obtido, o modo de bloqueio mantido at o fim da transao. Para examinar a lista de bloqueios correntemente mantidos pelo servidor de banco de dados, deve ser utilizada a viso do sistema pg_locks. Para obter mais informaes relativas ao monitoramento do status do subsistema de gerncia de bloqueios consulte o Guia do Administrador do PostgreSQL.

Modos de bloqueio no nvel de tabela: ACCESS SHARE Conflita apenas com o modo de bloqueio ACCESS EXCLUSIVE. O comando SELECT obtm um bloqueio deste modo nas tabelas referenciadas. Em geral, qualquer comando que apenas l a tabela sem modific-la obtm este modo de bloqueio. ROW SHARE Conflita com os modos de bloqueio EXCLUSIVE e ACCESS EXCLUSIVE. O comando SELECT FOR UPDATE obtm o bloqueio neste modo na(s) tabela(s) de destino (alm do bloqueio no modo ACCESS SHARE para as demais tabelas referenciadas mas no selecionadas FOR UPDATE). ROW EXCLUSIVE Conflita com os modos de bloqueio SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE e ACCESS EXCLUSIVE. os comandos UPDATE, DELETE e INSERT obtm este modo de bloqueio na tabela de destino (alm do modo de bloqueio ACCESS SHARE nas outras tabelas referenciadas). Em geral, este modo de bloqueio obtido por todos os comandos que modificam os dados da tabela. SHARE UPDATE EXCLUSIVE Conflita com os modos de bloqueio SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE e ACCESS EXCLUSIVE. Este modo protege a tabela contra mudanas concorrentes no esquema e a execuo do comando VACUUM. Obtida pelo comando VACUUM (sem a opo FULL). SHARE Conflita com os modos de bloqueio ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE ROW EXCLUSIVE, EXCLUSIVE e ACCESS EXCLUSIVE. Este modo protege a tabela contra mudanas concorrentes nos dados. Obtido pelo comando CREATE INDEX. SHARE ROW EXCLUSIVE Conflita com os modos de bloqueio ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE e ACCESS EXCLUSIVE. Este modo de bloqueio no obtido automaticamente por nenhum comando do PostgreSQL. EXCLUSIVE Conflita com os modos de bloqueio ROW SHARE, ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE e ACCESS EXCLUSIVE. Este modo permite apenas ACCESS SHARE concorrente, ou seja, somente leituras da tabela podem prosseguir em paralelo com uma transao que obteve este modo de bloqueio. Este modo de bloqueio no obtido automaticamente por nenhum comando do PostgreSQL.

ACCESS EXCLUSIVE Conflita com todos os modos de bloqueio (ACCESS SHARE, ROW SHARE, ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE e ACCESS EXCLUSIVE). Este modo garante que a transao que o obteve a nica acessando a tabela de qualquer forma. Obtido pelos comandos ALTER TABLE, DROP TABLE e VACUUM FULL. Este , tambm, o modo de bloqueio padro para o comando LOCK TABLE sem a especificao explcita do modo. Nota: Somente o bloqueio ACCESS EXCLUSIVE bloqueia o comando SELECT (sem a clusula FOR UPDATE).

3.2. Bloqueios no nvel de linha


Alm dos bloqueios no nvel de tabela, existem os bloqueios no nvel de linha. Um bloqueio no nvel de linha, para uma linha especfica, obtido automaticamente quando a linha atualizada (ou excluda ou marcada para atualizao). O bloqueio mantido at a transao efetivar ou desfazer as alteraes. Os bloqueios no nvel de linha no afetam a consulta aos dados; bloqueiam apenas escritas na mesma linha. Para obter um bloqueio no nvel de linha sem na verdade modificar a linha, deve-se selecionar a linha por meio do comando SELECT FOR UPDATE. Observe que, aps um determinado bloqueio ser obtido a transao pode atualizar a linha vrias vezes sem que haja conflito. O PostgreSQL no guarda nenhuma informao em memria relativa s linhas modificadas, portanto no existe limite no nmero de linhas bloqueadas de uma vez. Entretanto, o bloqueio de uma linha pode causar escrita no disco; por exemplo, o comando SELECT FOR UPDATE modifica as linhas selecionadas para marc-las ocasionando escrita no disco. Alm dos bloqueios de tabela e de linha, tambm so utilizados bloqueios no nvel de pgina, compartilhados e exclusivos, para controlar o acesso de leitura e gravao nas pginas da tabela no shared buffer pool. Estes bloqueios so liberados imediatamente aps a tupla ser lida ou atualizada. Normalmente os desenvolvedores de aplicao no precisam se preocupar com bloqueios no nvel de pgina, sendo mencionados para o assunto ficar completo.

3.3. Impasses
A utilizao de bloqueios explcitos pode causar impasses (deadlocks), em particular quando duas (ou mais) transaes mantm bloqueios que outra deseja. Por exemplo, se a transao 1 obtm um bloqueio exclusivo na tabela A e, ento, tenta obter um bloqueio exclusivo na tabela B, enquanto a transao 2 j possui um bloqueio exclusivo na tabela B, e agora tenta obter um bloqueio exclusivo na tabela A, ento nenhuma das duas transaes pode continuar. O PostgreSQL detecta automaticamente as situaes de impasse, resolvendo-as abortando uma das transaes envolvidas, permitindo que a(s) outra(s) prossiga(m) (Exatamente qual transao abortada difcil prever, no se devendo confiar nesta previso). Geralmente, a melhor defesa contra os impasses evit-los tendo certeza que todas as aplicaes que utilizam o banco de dados obtm estes bloqueios em vrios objetos em uma ordem consistente. Deve ser garantido, tambm, que o primeiro bloqueio de um objeto em uma transao seja aquele com o modo mais elevado que ser necessrio para este objeto. Se no for possvel conhecer esta situao antecipadamente, ento os impasses podem ser tratados em tempo de execuo tentando novamente a execuo das transaes abortadas pelos impasses.

Enquanto a situao de impasse no detectada, uma transao aguardando um bloqueio no nvel de tabela ou no nvel de linha fica aguardando indefinidamente pela liberao do bloqueio conflitante. Por isso, uma pssima idia as aplicaes manterem transaes abertas por longos perodos (por exemplo, aguardando a entrada de dados pelo usurio).

4. Verificao da consistncia dos dados no nvel da aplicao


Uma vez que a leitura no PostgreSQL no bloqueia os dados, sem levar em considerao o nvel de isolamento da transao, os dados lidos por uma transao podem ser sobrescritos por outra transao concorrente. Em outras palavras, se uma linha retornada pelo comando SELECT, isto no significa que esta linha ainda a linha corrente no instante em que retornada (ou seja, algum tempo depois que o comando corrente comeou). A linha pode ter sido modificada ou excluda por uma transao j efetivada, que efetuou esta efetivao aps a transao ter comeado. Mesmo que a linha ainda seja vlida "agora", esta linha pode ser mudada ou excluda antes da transao corrente efetivar ou desfazer suas modificaes. Outra maneira de pensar em relao a isto que cada transao enxerga um instantneo do contedo do banco de dados, e as transaes executando concorrentemente podem perfeitamente enxergar instantneos diferentes. Portanto, o prprio conceito de "agora" de alguma forma suspeito. Normalmente isto no um grande problema quando as aplicaes cliente esto isoladas uma das outras, mas se os clientes podem se comunicar por meio de canais por fora do banco de dados, ento srias confuses podem acontecer. Para garantir a validade corrente de uma linha e proteg-la contra atualizaes concorrentes, deve ser utilizado o comando SELECT FOR UPDATE ou uma declarao LOCK TABLE apropriada (o comando SELECT FOR UPDATE bloqueia apenas as linhas selecionadas contra atualizaes concorrentes, enquanto o LOCK TABLE bloqueia toda a tabela). Isto deve ser levado em considerao ao portar aplicaes de outros ambientes para o PostgreSQL. Nota: Antes da verso 6.5 o PostgreSQL utilizava bloqueios de leitura e, portanto, as consideraes acima tambm se aplicam quando feita uma atualizao de uma verso do PostgreSQL anterior a 6.5. As verificaes globais de validade requerem consideraes extras sob o MVCC. Por exemplo, uma aplicao bancria pode desejar verificar se a soma de todos os crditos em uma tabela igual a soma de todos os dbitos em outra tabela, no momento em que as duas tabelas esto sendo ativamente atualizadas. Comparar os resultados de dois comando SELECT SUM(...) sucessivos no vai funcionar confiavelmente no modo Read Committed, porque o segundo comando, provavelmente, inclui resultados de transaes que no apareciam no primeiro comando. Realizar as duas somas em uma mesma transao serializvel fornece uma imagem precisa dos efeitos das transaes efetivadas antes do incio da transao serializvel --- mas pode ser legitimamente questionado se a resposta ainda relevante na hora que foi produzida. Se a prpria transao serializvel introduziu algumas mudanas antes de tentar efetuar a verificao de consistncia, o valor prtico da verificao fica ainda mais discutvel, porque agora so includas algumas, mas no todas, as mudanas ocorridas aps o incio da transao. Em casos como este, uma pessoa cuidadosa pode desejar bloquear todas as tabelas necessrias para fazer a verificao, para obter uma imagem da situao atual acima de qualquer suspeita. Um bloqueio no modo SHARE (ou superior) garante no haver mudanas no efetivadas na tabela bloqueada, fora as mudanas efetuadas pela prpria transao corrente.

Observe tambm que, quando a preveno contra alteraes concorrentes est baseada em bloqueios explcitos, deve ser utilizado o modo Read Committed, ou tomar-se o cuidado de obter os bloqueios antes de executar os comandos no modo serializvel. Um bloqueio explcito obtido em uma transao serializvel garante que nenhuma outra transao modificando a tabela est executando --mas se o instantneo enxergado pela transao for anterior obteno do bloqueio, pode ser que seja anterior a algumas mudanas na tabela que agora esto efetivadas. Um instantneo de uma transao serializvel , na verdade, tirado no incio do primeiro comando (SELECT, INSERT, UPDATE ou DELETE) sendo, portanto, possvel obter o bloqueio explcito antes do instantneo ser tirado.

5. Bloqueio e ndices
Embora o PostgreSQL fornea acesso de leitura e gravao no bloqueante aos dados das tabelas, o acesso de leitura e gravao no bloqueante no oferecido, atualmente, para todos os mtodos de acessos dos ndices implementados pelo PostgreSQL. Os tipos de ndices existentes so implementados do seguinte modo: ndices B-tree So utilizados bloqueios compartilhados/exclusivos no nvel de pgina, de curta durao, para acesso de leitura/gravao. Os bloqueios so liberados imediatamente aps cada tupla do ndice ser lida ou inserida. Os ndices B-tree fornecem a concorrncia mais elevada sem condies de impasse. ndices GiST e R-tree So utilizados bloqueios compartilhados/exclusivos no nvel de ndice para acessos de leitura/gravao. Os bloqueios so liberados aps a declarao (comando) ser executada. ndices Hash So utilizados bloqueios compartilhados/exclusivos no nvel de pgina para acessos de leitura/gravao. Os bloqueios so liberados aps a pgina ser processada. Os bloqueios no nvel de pgina permitem uma concorrncia melhor que o bloqueio no nvel de ndice, mas podem ocasionar impasses. Em resumo, o ndice B-tree o tipo de ndice recomendado para as aplicaes correntes.

Você também pode gostar