Documentação do PostgreSQL 8.0.0 | ||||
---|---|---|---|---|
Anterior | Início | Capítulo 12. Controle de simultaneidade | Fim | Próxima |
O PostgreSQL fornece vários modos de bloqueio para controlar o acesso simultâneo aos dados nas tabelas. Estes modos podem ser utilizados para controlar o bloqueio pela transação, nas situações onde o MVCC não produz o comportamento adequado. Também, a maioria dos comandos do PostgreSQL obtém, automaticamente, bloqueios nos modos apropriados para garantir que as tabelas referenciadas não serão excluídas ou alteradas de forma incompatível enquanto o comando estiver executando (Por exemplo, o comando ALTER TABLE não pode executar simultaneamente com outras operações na mesma tabela).
Para examinar a lista de bloqueios ativos no servidor de banco de dados em um determinado instante, deve ser utilizada a visão do sistema pg_locks (Seção 42.33). Para obter informações adicionais sobre a monitoração do status do subsistema de gerência de bloqueios consulte o Capítulo 23.
A lista abaixo mostra os modos de bloqueio disponíveis, e os contextos onde estes modos são utilizados automaticamente pelo PostgreSQL. Também pode ser obtido explicitamente qualquer um destes níveis de bloqueio através do comando LOCK. Lembre-se que todos estes modos de bloqueio são no nível de tabela, mesmo que o nome contenha a palavra "row" (linha). Os nomes dos modos de bloqueio são históricos. De alguma forma os nomes refletem a utilização típica de cada modo de bloqueio — mas as semânticas são todas a mesma. A única diferença real entre um modo de bloqueio e outro é o conjunto de modos de bloqueio que cada um conflita. Duas transações não podem obter bloqueios com modos conflitantes na mesma tabela ao mesmo tempo (Entretanto, uma transação nunca conflita consigo mesma. Por exemplo, pode obter o bloqueio ACCESS EXCLUSIVE e posteriormente obter o bloqueio ACCESS SHARE na mesma tabela). Podem ser obtidos simultaneamente modos de bloqueio não conflitantes por muitas transações. Em particular, deve ser observado que alguns modos de bloqueio são autoconflitantes (por exemplo, o modo de bloqueio ACCESS EXCLUSIVE não pode ser obtido por mais de uma transação ao mesmo tempo), enquanto outros não são autoconflitantes (por exemplo, o modo de bloqueio ACCESS SHARE pode ser obtido por várias transações ao mesmo tempo). Uma vez obtido, o modo de bloqueio permanece até o fim da transação.
Modos de bloqueio no nível de tabela
Conflita apenas com o modo de bloqueio ACCESS EXCLUSIVE.
O comando SELECT e o comando ANALYZE obtêm um bloqueio neste modo nas tabelas referenciadas. Em geral, qualquer comando que apenas lê a tabela sem modificá-la obtém este modo de bloqueio.
Conflita com os modos de bloqueio EXCLUSIVE e ACCESS EXCLUSIVE.
O comando SELECT FOR UPDATE obtém o bloqueio neste modo na(s) tabela(s) de destino (além do bloqueio no modo ACCESS SHARE para as demais tabelas referenciadas mas não selecionadas FOR UPDATE).
Conflita com os modos de bloqueio SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE e ACCESS EXCLUSIVE.
Os comandos UPDATE, DELETE e INSERT obtêm este modo de bloqueio na tabela de destino (além do modo de bloqueio ACCESS SHARE nas outras tabelas referenciadas). Em geral, este modo de bloqueio é obtido por todos os comandos que alteram os dados da tabela.
Conflita com os modos de bloqueio SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE e ACCESS EXCLUSIVE. Este modo protege a tabela contra alterações simultâneas no esquema e a execução do comando VACUUM.
Obtida pelo comando VACUUM (sem a opção FULL).
Conflita com os modos de bloqueio ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE ROW EXCLUSIVE, EXCLUSIVE e ACCESS EXCLUSIVE. Este modo protege a tabela contra alterações simultâneas nos dados.
Obtido pelo comando CREATE INDEX.
Conflita com os modos de bloqueio ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE e ACCESS EXCLUSIVE.
Este modo de bloqueio não é obtido automaticamente por nenhum comando do PostgreSQL.
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 bloqueios ACCESS SHARE simultâneos, ou seja, somente leituras da tabela podem prosseguir em paralelo com uma transação que obteve este modo de bloqueio.
Este modo de bloqueio não é obtido automaticamente por nenhum comando do PostgreSQL. Entretanto, é obtido em alguns catálogos do sistema em algumas operações.
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 transação que o obteve é a única acessando a tabela de qualquer forma.
Obtido pelos comandos ALTER TABLE, DROP TABLE, REINDEX, CLUSTER e VACUUM FULL. Este é, também, o modo de bloqueio padrão para o comando LOCK TABLE sem a especificação explícita do modo.
Dica: Somente o bloqueio ACCESS EXCLUSIVE bloqueia o comando SELECT (sem a cláusula FOR UPDATE).
Além dos bloqueios no nível de tabela, existem os bloqueios no nível de linha. O bloqueio no nível de linha é obtido automaticamente para uma linha específica quando a linha é atualizada (ou excluída ou marcada para atualização). O bloqueio é mantido até a transação efetivar ou desfazer as alterações. Os bloqueios no nível de linha não afetam a consulta aos dados; bloqueiam apenas escritas na mesma linha. Para obter um bloqueio no nível de linha sem na verdade modificá-la, a linha deve ser selecionada por meio do comando SELECT FOR UPDATE. Deve ser observado que após ser obtido um bloqueio no nível de linha, a transação pode atualizar esta linha várias vezes sem medo de conflito.
O PostgreSQL não guarda em memória qualquer informação sobre as linhas alteradas, portanto não existe limite de número de linhas bloqueadas de cada vez. Entretanto, o bloqueio de uma linha pode causar escrita no disco; por exemplo, o comando SELECT FOR UPDATE altera as linhas selecionadas para marcá-las, ocasionando escrita em disco.
Além dos bloqueios de tabela e de linha, também são utilizados bloqueios compartilhados e exclusivos no nível de página, para controlar o acesso de leitura e escrita nas páginas da tabela no shared buffer pool. Estes bloqueios são liberados imediatamente após a linha ser lida ou atualizada. Normalmente os desenvolvedores de aplicativos não precisam se preocupar com bloqueios no nível de página, sendo mencionados somente para o assunto ficar completo.
A utilização de bloqueios explícitos pode aumentar a probabilidade de acontecerem impasses (deadlocks), onde duas (ou mais) transações mantêm bloqueios que a outra deseja. Por exemplo, se a transação 1 obtiver um bloqueio exclusivo na tabela A e, depois, tentar obter um bloqueio exclusivo na tabela B, enquanto a transação 2 já possui um bloqueio exclusivo na tabela B, e agora deseja obter um bloqueio exclusivo na tabela A, então nenhuma das duas transações pode prosseguir. O PostgreSQL detecta automaticamente as situações de impasse, resolvendo-as interrompendo uma das transações envolvidas, permitindo que a(s) outra(s) prossiga(m) (Exatamente qual transação será interrompida é difícil prever, não se devendo confiar nesta previsão).
Deve ser observado que os impasses também podem ocorrer como resultado de um bloqueio no nível de linha (e, por isso, podem ocorrer mesmo que não se use bloqueios explícitos). Considere o caso onde existam duas transações simultâneas alterando uma tabela. A primeira transação executa:
UPDATE conta SET saldo = saldo + 100.00 WHERE num_conta = 11111;
Este comando obtém um bloqueio no nível de linha na linha com o número da conta especificado. Depois a segunda transação executa:
UPDATE conta SET saldo = saldo + 100.00 WHERE num_conta = 22222; UPDATE conta SET saldo = saldo - 100.00 WHERE num_conta = 11111;
O primeiro comando UPDATE é bem-sucedido ao obter o bloqueio no nível de linha na linha especificada e, portanto, prossegue atualizando a linha. Entretanto, o segundo comando UPDATE descobre que a linha a ser atualizada está bloqueada e, portanto, fica aguardando a transação que obteve o bloqueio terminar. A transação 2 agora está aguardando a transação 1 completar para poder continuar. Agora, a transação 1 executa:
UPDATE conta SET saldo = saldo - 100.00 WHERE num_conta = 22222;
A transação 1 tenta obter um bloqueio no nível de linha na linha especificada, mas não pode: a transação 2 já possui este bloqueio. Portanto, fica aguardando a transação 2 terminar. Assim sendo, a transação 1 está bloqueada pela transação 2, e a transação 2 está bloqueada pela transação 1: uma condição de impasse. O PostgreSQL detecta esta situação e interrompe uma das transações.
Geralmente a melhor defesa contra os impasses é evitá-los, tendo certeza que todos os aplicativos que utilizam o banco de dados obtêm bloqueios em vários objetos em uma ordem consistente. No exemplo anterior, se as duas transações tivessem atualizado as linhas na mesma ordem, não teria ocorrido nenhum impasse. Deve-se garantir, também, que o primeiro bloqueio obtido em um objeto em uma transação seja aquele com o modo mais alto que será necessário para este objeto. Se for impraticável verificar esta situação antecipadamente, então os impasses podem ser tratados em tempo de execução tentando executar novamente as transações interrompidas pelos impasses.
Enquanto a situação de impasse não for detectada, uma transação em busca de um bloqueio no nível de tabela ou no nível de linha ficará aguardando indefinidamente pela liberação dos bloqueios conflitantes. Isso significa que é uma péssima idéia os aplicativos manterem transações abertas por longos períodos de tempo (por exemplo, aguardando a entrada de dados pelo usuário).