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.
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 lê
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.
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 solicitado | Modo de bloqueio existente | |||||||
|---|---|---|---|---|---|---|---|---|
ACCESS SHARE | ROW SHARE | ROW EXCL. | SHARE UPDATE EXCL. | SHARE | SHARE ROW EXCL. | EXCL. | ACCESS EXCL. | |
ACCESS SHARE | X | |||||||
ROW SHARE | X | X | ||||||
ROW EXCL. | X | X | X | X | ||||
SHARE UPDATE EXCL. | X | X | X | X | X | |||
SHARE | X | X | X | X | X | |||
SHARE ROW EXCL. | X | X | X | X | X | X | ||
EXCL. | X | X | X | X | X | X | X | |
ACCESS EXCL. | X | X | X | X | X | X | X | X |
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 solicitado | Modo de bloqueio corrente | |||
|---|---|---|---|---|
| FOR KEY SHARE | FOR SHARE | FOR NO KEY UPDATE | FOR UPDATE | |
| FOR KEY SHARE | X | |||
| FOR SHARE | X | X | ||
| FOR NO KEY UPDATE | X | X | X | |
| FOR UPDATE | X | X | X | X |
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.
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).
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.)