13.3. Bloqueio explícito #

13.3.1. Bloqueio no nível de tabela
13.3.2. Modos de bloqueio no nível de linha
13.3.3. Bloqueio no nível de página
13.3.4. Impasses (deadlocks)
13.3.5. Bloqueios informativos

O PostgreSQL fornece vários modos de bloqueio para controlar o acesso concorrente aos dados nas tabelas. Esses modos podem ser usados para bloqueio controlado pela aplicação em situações onde o MVCC não apresenta o comportamento desejado. Além disso, a maioria dos comandos do PostgreSQL adquirem automaticamente bloqueios nos modos apropriados para garantir que as tabelas referenciadas não sejam excluídas ou modificadas de maneiras incompatíveis enquanto o comando é executado. (Por exemplo, o comando TRUNCATE não pode ser executado com segurança concorrentemente com outras operações na mesma tabela, então o comando adquire o bloqueio ACCESS EXCLUSIVE na tabela para garantir a execução adequada.)

Para examinar a lista dos bloqueios pendentes naquele momento em um servidor de banco de dados, é usada a visão do sistema. pg_locks. Para obter mais informações sobre como monitorar o status do subsistema gerenciador de bloqueios, consulte o Capítulo 27.

13.3.1. Bloqueio no nível de tabela #

A lista abaixo mostra os modos de bloqueio disponíveis, e os contextos nos quais são usados automaticamente pelo PostgreSQL. Também é possível obter qualquer um destes bloqueios explicitamente usando o comando LOCK. Lembre-se que todos estes modos de bloqueio são bloqueios no nível de tabela, mesmo que o nome contenha a palavra linha; os nomes dos modos de bloqueio são históricos. Até certo ponto, os nomes refletem o uso típico de cada modo de bloqueio — mas a semântica é a mesma. A única diferença real entre um modo de bloqueio e outro, é o conjunto de modos de bloqueio com o qual cada um entra em conflito (veja a Tabela 13.2). Duas transações não podem possuir bloqueios de modos conflitantes na mesma tabela ao mesmo tempo. (Entretanto, uma transação nunca entra em conflito consigo mesma. Por exemplo, pode obter o bloqueio ACCESS EXCLUSIVE e, posteriormente, obter o bloqueio ACCESS SHARE na mesma tabela.) Os modos de bloqueio não conflitantes podem ser possuídos concorrentemente por muitas transações. Note em particular que alguns modos de bloqueio são autoconflitantes (por exemplo, o bloqueio ACCESS EXCLUSIVE não pode ser possuído por mais de uma transação por vez), enquanto outros modos não são autoconflitantes (por exemplo, o bloqueio ACCESS SHARE pode ser possuído por várias transações).

Modos de bloqueio no nível de tabela

ACCESS SHARE (AccessShareLock)

Conflita apenas com o modo de bloqueio ACCESS EXCLUSIVE.

O comando SELECT adquire o bloqueio nesse modo nas tabelas referenciadas. Em geral, qualquer consulta que apenas uma tabela, e não a modifique, adquire este modo de bloqueio.

ROW SHARE (RowShareLock)

Conflita com os modos de bloqueio EXCLUSIVE e ACCESS EXCLUSIVE.

O comando SELECT adquire um bloqueio deste modo em todas as tabelas nas quais for especificada uma entre as opções FOR UPDATE, FOR NO KEY UPDATE, FOR SHARE ou FOR KEY SHARE (além dos bloqueios ACCESS SHARE em quaisquer outras tabelas que são referenciadas sem qualquer menção explícita a FOR UPDATE/FOR SHARE).

ROW EXCLUSIVE (RowExclusiveLock)

Conflita com os modos de bloqueio SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE e ACCESS EXCLUSIVE.

Os comandos UPDATE, DELETE, INSERT e MERGE adquirem este modo de bloqueio na tabela de destino (além dos bloqueios ACCESS SHARE em quaisquer outras tabelas referenciadas). Em geral, este modo de bloqueio será adquirido por qualquer comando que modifique dados em uma tabela.

SHARE UPDATE EXCLUSIVE (ShareUpdateExclusiveLock)

Conflita com os modos de bloqueio SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE e ACCESS EXCLUSIVE. Esse modo protege a tabela contra alterações concorrentes do esquema, e a execução do comando VACUUM.

Adquirido por VACUUM (sem a opção FULL), ANALYZE, CREATE INDEX CONCURRENTLY, CREATE STATISTICS, COMMENT ON, REINDEX CONCURRENTLY, e certas variantes de ALTER INDEX e ALTER TABLE (para obter detalhes completos veja a documentação destes comandos).

SHARE (ShareLock)

Conflita com os modos de bloqueio ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE ROW EXCLUSIVE, EXCLUSIVE e ACCESS EXCLUSIVE. Esse modo protege a tabela contra alterações de dados concorrentes.

Adquirido por CREATE INDEX (sem a opção CONCURRENTLY).

SHARE ROW EXCLUSIVE (ShareRowExclusiveLock)

Conflita com os modos de bloqueio ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE e ACCESS EXCLUSIVE. Esse modo protege uma tabela contra alterações de dados concorrentes, sendo autoexclusivo para que apenas uma sessão possa mantê-lo por vez.

Adquirido por CREATE TRIGGER e algumas formas de ALTER TABLE.

EXCLUSIVE (ExclusiveLock)

Conflita com os modos de bloqueio ROW SHARE, ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE e ACCESS EXCLUSIVE. Esse modo permite apenas bloqueios ACCESS SHARE concorrentes, ou seja, somente leituras de tabela podem prosseguir em paralelo com uma transação que possui este modo de bloqueio.

Adquirido por REFRESH MATERIALIZED VIEW CONCURRENTLY.

ACCESS EXCLUSIVE (AccessExclusiveLock)

Conflita com bloqueios de todos os modos (ACCESS SHARE, ROW SHARE, ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE e ACCESS EXCLUSIVE). Esse modo garante que o possuidor seja a única transação acessando a tabela de alguma forma.

Adquirido por DROP TABLE, TRUNCATE, REINDEX, CLUSTER, VACUUM FULL e REFRESH MATERIALIZED VIEW (sem a opção CONCURRENTLY). Muitas formas de ALTER INDEX e ALTER TABLE também adquirem o bloqueio neste modo. Esse é também o modo de bloqueio padrão para comandos LOCK TABLE que não especificam o modo explicitamente.

Dica

Apenas o bloqueio ACCESS EXCLUSIVE bloqueia um comando SELECT (sem a opção FOR UPDATE/SHARE).

Uma vez adquirido, o bloqueio é normalmente mantido até o final da transação. Mas se o bloqueio for adquirido após estabelecer um ponto de salvamento, o bloqueio será liberado imediatamente se o ponto de salvamento for desfeito. Isto é consistente com o princípio que o comando ROLLBACK cancela todos os efeitos dos comandos desde o ponto de salvamento. O mesmo vale para bloqueios adquiridos em um bloco de exceção PL/pgSQL: uma escape de erro do bloco libera os bloqueios adquiridos dentro dele.

Tabela 13.2. Modos de bloqueio conflitantes

Modo de bloqueio solicitadoModo de bloqueio existente
ACCESS SHAREROW SHAREROW EXCL.SHARE UPDATE EXCL.SHARESHARE ROW EXCL.EXCL.ACCESS EXCL.
ACCESS SHARE       X
ROW SHARE      XX
ROW EXCL.    XXXX
SHARE UPDATE EXCL.   XXXXX
SHARE  XX XXX
SHARE ROW EXCL.  XXXXXX
EXCL. XXXXXXX
ACCESS EXCL.XXXXXXXX

13.3.2. Modos de bloqueio no nível de linha #

Além dos bloqueios no nível de tabela, existem bloqueios no nível de linha, listados abaixo com os contextos onde são usados automaticamente pelo PostgreSQL. Veja Tabela 13.3 para obter uma relação completa de conflitos de bloqueio no nível de linha. Note que uma transação pode conter bloqueios conflitantes na mesma linha, mesmo em diferentes subtransações; mas fora isso, duas transações nunca podem conter bloqueios conflitantes na mesma linha. Os bloqueios no nível de linha não afetam a consulta aos dados; bloqueiam apenas escritas e bloqueios na mesma linha. Os bloqueios no nível de linha são liberados no final da transação, ou durante a reversão do ponto de salvamento, assim como os bloqueios no nível de tabela.

Modos de bloqueio no nível de linha

FOR UPDATE

FOR UPDATE faz com que as linhas recuperadas pela instrução SELECT sejam bloqueadas como se fossem para atualização. Isto evita que sejam bloqueadas, modificadas ou excluídas por outras transações, até que a transação corrente termine. Ou seja, as demais transações que tentarem executar UPDATE, DELETE, SELECT FOR UPDATE, SELECT FOR NO KEY UPDATE, SELECT FOR SHARE ou SELECT FOR KEY SHARE nessas linhas, serão bloqueadas até que a transação corrente termine; inversamente, SELECT FOR UPDATE espera até que a transação concorrente que executou qualquer um destes comandos na mesma linha termine e, em seguida, bloqueia e retorna a linha atualizada (ou nenhuma linha, se a linha foi excluída). Entretanto, dentro de uma transação REPEATABLE READ ou SERIALIZABLE, será lançado um erro se uma linha a ser bloqueada for alterada depois do início da transação. Para obter mais detalhes veja a Seção 13.4.

O modo de bloqueio FOR UPDATE também é adquirido por qualquer comando DELETE numa linha e, também, por um comando UPDATE que modifica os valores de certas colunas. No momento, o conjunto de colunas considerado para o caso do comando UPDATE são aqueles que possuem um índice único que pode ser usado em uma chave estrangeira (portanto, índices parciais e índices de expressão não são considerados), mas isto pode mudar no futuro.

FOR NO KEY UPDATE

Se comporta de forma semelhante a FOR UPDATE, exceto pelo bloqueio adquirido ser mais fraco: este bloqueio não bloqueia comandos SELECT FOR KEY SHARE que tentam obter bloqueio nas mesmas linhas. Esse modo de bloqueio também é adquirido por qualquer comando UPDATE que não requeira um bloqueio FOR UPDATE.

FOR SHARE

Se comporta de forma semelhante a FOR NO KEY UPDATE, exceto por obter bloqueio compartilhado, em vez de bloqueio exclusivo, em cada linha recuperada. Um bloqueio compartilhado impede que outras transações executem UPDATE, DELETE, SELECT FOR UPDATE ou SELECT FOR NO KEY UPDATE nessas linhas, mas não as impede de realizar SELECT FOR SHARE ou SELECT FOR KEY SHARE.

FOR KEY SHARE

Se comporta de forma semelhante a FOR SHARE, exceto pelo bloqueio ser mais fraco: SELECT FOR UPDATE é bloqueado, mas não SELECT FOR NO KEY UPDATE. Um bloqueio FOR KEY SHARE impede que outras transações executem DELETE, ou qualquer UPDATE que altere os valores da chave, mas não outro UPDATE, e também não impede SELECT FOR NO KEY UPDATE, SELECT FOR SHARE, ou SELECT FOR KEY SHARE.

O PostgreSQL não mantém nenhuma informação sobre linhas modificadas na memória, então não há limite no número de linhas bloqueadas de uma vez. Entretanto, bloquear uma linha pode causar escrita no disco, por exemplo, SELECT FOR UPDATE modifica as linhas selecionadas para marcá-las como bloqueadas e, portanto, resulta em escrita no disco.

Tabela 13.3. Bloqueios conflitantes no nível de linha

Modo de bloqueio solicitadoModo de bloqueio corrente
FOR KEY SHAREFOR SHAREFOR NO KEY UPDATEFOR UPDATE
FOR KEY SHARE   X
FOR SHARE  XX
FOR NO KEY UPDATE XXX
FOR UPDATEXXXX

13.3.3. Bloqueio no nível de página #

Além dos bloqueios de tabela e de linha, são usados bloqueios exclusivos/compartilhados no nível de página para controlar o acesso de leitura/escrita às páginas da tabela no conjunto de buffers compartilhado. Estes bloqueios são liberados imediatamente após a linha ser recuperada ou atualizada. Normalmente os desenvolvedores de aplicações não precisam se preocupar com bloqueios no nível de página, mas são mencionados aqui para ficar completo.

13.3.4. Impasses (deadlocks) #

O uso de bloqueio explícito pode aumentar a probabilidade de impasses (deadlocks), onde duas (ou mais) transações detêm bloqueios que a outra deseja. Por exemplo, se a transação 1 adquiriu o bloqueio exclusivo da tabela A e, em seguida, tentar obter o bloqueio exclusivo da tabela B, enquanto a transação 2, que já possui o bloqueio exclusivo da tabela B, agora quer o bloqueio exclusivo da tabela A, então nenhuma das duas transações pode prosseguir. O PostgreSQL detecta automaticamente situações de impasse, e as resolve interrompendo uma das transações envolvidas, permitindo que as outras sejam concluídas. (Exatamente qual transação será interrompida é difícil prever, e não se deve confiar nessa previsão.)

Note que também podem ocorrer impasses como resultado de bloqueios no nível de linha (portanto, podem ocorrer mesmo se não for usado bloqueio explícito). Considere o caso onde duas transações simultâneas modificam uma tabela. A primeira transação executada é:

UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 11111;

Esse comando adquire o bloqueio no nível de linha na linha com o número de conta especificado. Então, a segunda transação é executada:

UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 22222;
UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 11111;

A primeira instrução UPDATE adquire com sucesso o bloqueio no nível de linha na linha especificada, portanto, consegue atualizar essa linha. Entretanto, a segunda instrução UPDATE descobre que a linha que está tentando atualizar está bloqueada, portanto, aguarda o término da transação que adquiriu o bloqueio. Agora a transação 2 está aguardando o término da transação 1 para poder continuar sua execução. Neste momento, a transação 1 executa:

UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 22222;

A transação 1 tenta obter o bloqueio no nível de linha na linha especificada, mas não pode: a transação 2 já possui este bloqueio. Então, aguarda a conclusão da transação 2. Assim, 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 (deadlock). O PostgreSQL detecta essa situação e interrompe uma das transações.

Geralmente a melhor defesa contra impasses é evitá-los, assegurando-se que todas as aplicações que usam o banco de dados adquiram bloqueios em vários objetos em uma ordem consistente. No exemplo acima, se as duas transações tivessem atualizado as linhas na mesma ordem, nenhum impasse teria ocorrido. Deve-se, também, garantir que o primeiro bloqueio adquirido em um objeto numa transação seja o modo mais restritivo que será necessário para este objeto. Se não for viável verificar isto com antecedência, os impasses podem ser tratados dinamicamente, repetindo as transações interrompidas devido a impasses.

Desde que nenhuma situação de impasse seja detectada, uma transação que busca o bloqueio no nível de tabela, ou no nível de linha, aguardará indefinidamente pela liberação de bloqueios conflitantes, significando que é uma má ideia as aplicações manterem transações abertas por longos períodos de tempo (por exemplo, enquanto aguarda a entrada de dados do usuário).

13.3.5. Bloqueios informativos #

O PostgreSQL fornece meios para criar bloqueios que possuem significados definidos pela aplicação. São chamados de bloqueios consultivos (advisory locks) [104], porque o sistema não impõe seu uso — cabe à aplicação usá-los corretamente. Os bloqueios consultivos podem ser úteis para estratégias de bloqueio inadequadas para o modelo MVCC. Por exemplo, um uso comum de bloqueios consultivos é emular estratégias de bloqueio pessimistas típicas dos chamados sistemas de gerenciamento de dados de arquivo simples. Enquanto um sinalizador armazenado em uma tabela possa ser usado para a mesma finalidade, os bloqueios consultivos são mais rápidos, evitam o inchamento da tabela, e são automaticamente limpos pelo servidor no final da sessão.

Existem duas maneiras de obter bloqueios consultivos no PostgreSQL: no nível de sessão, ou no nível de transação. Uma vez adquirido no nível de sessão, o bloqueio consultivo é mantido até que seja liberado explicitamente, ou a sessão termine. Ao contrário das solicitações de bloqueio padrão, as solicitações de bloqueios consultivos no nível de sessão não respeitam a semântica da transação: o bloqueio adquirido durante uma transação que é posteriormente desfeita ainda será mantido após o ROLLBACK e, da mesma forma, o bloqueio liberado permanece válido mesmo se a transação de chamada falhar posteriormente. Um bloqueio pode ser adquirido várias vezes pelo processo que o mantém; para cada solicitação de bloqueio concluída, deve haver uma solicitação de desbloqueio correspondente antes que o bloqueio seja realmente liberado. As solicitações de bloqueio no nível de transação, por outro lado, se comportam mais como solicitações de bloqueio regulares: são liberados automaticamente ao final da transação, não havendo operação de desbloqueio explícita. Esse comportamento é geralmente mais conveniente que o comportamento no nível de sessão para uso de curto prazo de um bloqueio consultivo. As solicitações de bloqueio no nível de sessão, e no nível de transação, para o mesmo identificador de bloqueio consultivo, bloquearão um ao outro da maneira esperada. Se a sessão já possuir um determinado bloqueio consultivo, as solicitações adicionais feitas por ela sempre serão bem-sucedidas, mesmo que outras sessões estejam aguardando o bloqueio; isto é verdade independentemente do nível (sessão ou transação) do bloqueio mantido e dos bloqueios solicitados.

Assim como para os demais bloqueios no PostgreSQL, a lista completa de bloqueios consultivos atualmente mantidos por qualquer sessão pode ser encontrada na visão do sistema pg_locks.

Tanto os bloqueios consultivos quanto os bloqueios regulares são armazenados em um conjunto de memória compartilhada, cujo tamanho é definido pelas variáveis de configuração max_locks_per_transaction e max_connections. Deve-se tomar cuidado para não esgotar essa memória, ou o servidor não poderá conceder mais nenhum bloqueio. Isto impõe um limite superior ao número de bloqueios consultivos que podem ser concedidos pelo servidor, geralmente de dezenas a centenas de milhares, dependendo de como o servidor está configurado.

Em certos casos usando métodos de bloqueio consultivo, principalmente em consultas envolvendo cláusulas de ordenação explícita e LIMIT, deve-se tomar cuidado para controlar os bloqueios adquiridos devido à ordem em que as expressões SQL são avaliadas. Por exemplo:

SELECT pg_advisory_lock(id) FROM foo WHERE id = 12345; -- ok
SELECT pg_advisory_lock(id) FROM foo WHERE id > 12345 LIMIT 100; -- perigo!
SELECT pg_advisory_lock(q.id) FROM
(
  SELECT id FROM foo WHERE id > 12345 LIMIT 100
) q; -- ok

Nas consultas acima, a segunda forma é perigosa, por não haver garantias que LIMIT será aplicado antes da execução da função de bloqueio. Isto pode fazer com que sejam adquiridos alguns bloqueios que a aplicação não esperava e, portanto, falharia em liberar (até terminar a sessão). Do ponto de vista da aplicação estes bloqueios permanecem pendentes, embora sejam visíveis em pg_locks.

As funções fornecidas para tratar bloqueios consultivos estão descritas na Seção 9.28.10.



[104] advisory lock: Os bloqueios consultivos do PostgreSQL são bloqueios cooperativos no nível de aplicação, explicitamente bloqueados e desbloqueados pelo código da aplicação do usuário. Uma aplicação pode usar bloqueios consultivos do PostgreSQL para coordenar atividades entre múltiplas sessões. Ao contrário dos bloqueios regulares no nível de objeto ou de linha, a aplicação tem controle total sobre o tempo de vida do bloqueio. AWS – Lock:advisory (N. T.)