Uma transação PostgreSQL aberta e somente para leitura pode bloquear DROP TABLE porque seu SELECT mantém um bloqueio de tabela ACCESS SHARE até o fim da transação. A exclusão precisa de um bloqueio conflitante ACCESS EXCLUSIVE, então aguarda — mesmo depois que a consulta retorna. PostgreSQL: bloqueio explícito

Este guia mostra como reproduzir essa espera em um Mac, identificar a sessão bloqueadora e encerrar a transação corretamente em node-postgres. Ele usa apenas linhas de artigo sintéticas. O SQL tem como alvo o PostgreSQL 18, a versão servida pela documentação oficial atual consultada para este artigo; consulte o manual correspondente à versão do seu servidor antes de aplicar configurações específicas de versão.

O que significa somente para leitura para os bloqueios do PostgreSQL?

READ ONLY restringe quais comandos uma transação pode executar. Por exemplo, impede modificações em tabelas comuns e comandos como DROP TABLE. Isso não isenta as leituras de bloqueios de tabela. Neste cenário, o leitor e a sessão que tenta excluir a tabela estão em transações separadas. PostgreSQL: SET TRANSACTION

Um SELECT simples adquire automaticamente ACCESS SHARE nas tabelas referenciadas. DROP TABLE exige ACCESS EXCLUSIVE, que conflita com esse bloqueio. Os bloqueios normalmente duram até o fim da transação; uma exceção documentada é reverter até um ponto de salvamento estabelecido antes da aquisição de um bloqueio. O exemplo abaixo não usa pontos de salvamento. PostgreSQL: bloqueio explícito

A distinção principal é entre a conclusão da consulta e a conclusão da transação. Ver linhas de artigo no terminal indica que a consulta retornou. Confira se a transação que a envolve também terminou. Em psql, o autocommit normalmente está habilitado, mas um BEGIN explícito mantém a transação aberta até que você a encerre. PostgreSQL: psql

Como reproduzir a exclusão bloqueada em um Mac?

Use um banco de dados de teste local PostgreSQL 18 já existente, com psql disponível no Terminal do seu Mac. Abra três abas de terminal conectadas ao mesmo banco de dados. Chame-as de A, B e C. Elas precisam ser conexões separadas; abas separadas do editor, por si só, não garantem isso.

O comando de conexão a seguir pressupõe que seu banco de dados de teste local se chama postgres. Se necessário, substitua pelo nome do seu banco de teste existente. Execute o comando em cada aba. A opção -X evita carregar um arquivo pessoal de inicialização psql. PostgreSQL: psql

psql -X -d postgres

Em cada conexão, habilite o autocommit explicitamente e confira a versão do servidor. Os blocos abaixo formam um passo a passo ordenado: os blocos seguintes dependem da configuração e do estado das sessões estabelecidos anteriormente.

\set AUTOCOMMIT on
SHOW server_version;

Crie a tabela de artigos sintéticos

Na aba C, execute a configuração uma vez. Use um nome de tabela não utilizado: se public.article_lock_demo já existir, pare e escolha outro nome, usando-o consistentemente nos exemplos. A configuração intencionalmente não remove uma tabela existente.

CREATE TABLE public.article_lock_demo (
    id integer PRIMARY KEY,
    title text NOT NULL
);

INSERT INTO public.article_lock_demo (id, title)
VALUES
    (1, 'Choosing a notebook'),
    (2, 'Organizing a reading list');

Mantenha aberta a transação do leitor

Na aba A, execute este bloco e deixe a conexão no prompt. Ainda não confirme a transação.

SET application_name = 'article_reader_demo';
BEGIN READ ONLY;
SHOW transaction_read_only;
SELECT id, title
FROM public.article_lock_demo
ORDER BY id;

Espere que transaction_read_only mostre on, seguido pelas duas linhas sintéticas. A leitura foi concluída, mas a transação explícita continua aberta.

Tente excluir a tabela de outra conexão

Na aba B, execute o bloco a seguir. Envie DROP TABLE como a última instrução, sem acrescentar outro comando.

SET application_name = 'article_drop_demo';
BEGIN;
SET LOCAL lock_timeout = '60s';
DROP TABLE public.article_lock_demo;

A exclusão deve aguardar enquanto A permanecer aberta. O limite ilustrativo de 60 segundos dá tempo para inspecionar a espera; não é uma configuração universal recomendada. lock_timeout cancela uma instrução que aguarda tempo demais para adquirir um bloqueio e se aplica separadamente a cada tentativa de aquisição de bloqueio. PostgreSQL: padrões de conexão do cliente

Mantenha aberta a transação explícita de B depois que a exclusão retornar. Mais tarde, reverta essa exclusão de demonstração para preservar a tabela, mesmo que a exclusão tenha sucesso. DROP TABLE remove a tabela e seus índices e restrições associados quando a transação é confirmada. PostgreSQL: DROP TABLE

Como comprovar qual sessão está bloqueando a exclusão?

Enquanto B aguarda, execute a consulta seguinte em C. Ela identifica as duas conexões de demonstração e pergunta ao PostgreSQL quais sessões estão bloqueando a solicitação de bloqueio de cada conexão. pg_blocking_pids() retorna uma matriz de IDs de processos bloqueadores ou uma matriz vazia quando o processo não está bloqueado. PostgreSQL: funções e operadores de informação do sistema

SELECT
    pid,
    application_name,
    state,
    xact_start,
    wait_event_type,
    wait_event,
    pg_blocking_pids(pid) AS blocking_pids,
    query
FROM pg_stat_activity
WHERE datname = current_database()
  AND application_name IN (
      'article_reader_demo',
      'article_drop_demo'
  )
ORDER BY application_name;

Espere que A apareça como idle in transaction: ela tem uma transação aberta, mas não está executando uma consulta. B pode aparecer como active enquanto aguarda, com um tipo de evento de espera Lock. O PostgreSQL documenta o estado de execução e os eventos de espera como independentes; portanto, apenas active não demonstra que um comando esteja progredindo. PostgreSQL: sistema de estatísticas cumulativas

Em seguida, inspecione as solicitações reais de bloqueio da tabela. Execute isto em C antes de encerrar a transação de A.

SELECT
    a.application_name,
    l.pid,
    l.mode,
    l.granted
FROM pg_locks AS l
JOIN pg_stat_activity AS a ON a.pid = l.pid
WHERE l.locktype = 'relation'
  AND l.database = (
      SELECT oid
      FROM pg_database
      WHERE datname = current_database()
  )
  AND l.relation = 'public.article_lock_demo'::regclass
ORDER BY a.application_name, l.mode;

O padrão esperado abaixo é ilustrativo, não uma saída de execução capturada. Em pg_locks, granted = true significa que o processo mantém o bloqueio; false significa que está aguardando por ele. PostgreSQL: pg_locks

Sessão Modo de bloqueio granted Interpretação
article_reader_demo AccessShareLock true O leitor mantém seu bloqueio na tabela
article_drop_demo AccessExclusiveLock false A exclusão aguarda seu bloqueio na tabela

Use as duas consultas juntas como uma sequência de diagnóstico: primeiro confirme a relação de bloqueio e, em seguida, inspecione os bloqueios mantidos e solicitados na tabela relevante. Uma sessão estar ociosa não é evidência suficiente por si só; a relação e as linhas de bloqueio a vinculam a essa exclusão específica.

Se B já tiver atingido o tempo limite, sua solicitação de bloqueio em espera não aparecerá mais. Execute ROLLBACK; em B, repita o bloco de transação de B e inspecione logo em seguida, enquanto A ainda estiver aberta.

Como liberar o leitor e concluir a demonstração?

Em A, encerre a transação somente para leitura:

COMMIT;

Se B ainda estiver aguardando e A era seu único bloqueador, a exclusão poderá ser concluída. Em B, desfaça imediatamente essa exclusão de demonstração para manter a tabela sintética disponível:

ROLLBACK;

Se B atingiu o tempo limite antes de você confirmar A, execute primeiro ROLLBACK; em B. Depois que A terminar, repita o bloco de B e então execute ROLLBACK; novamente quando a exclusão tiver sucesso. Isso cobre os dois resultados possíveis sem deixar a transação de B inacabada.

O auxílio à decisão a seguir ajuda a distinguir um problema de duração da transação de outra explicação. Os rótulos de atividade seguem os estados de sessão documentados pelo PostgreSQL. PostgreSQL: sistema de estatísticas cumulativas

Observação Próxima ação
A está idle in transaction e a matriz de bloqueadores de B contém o PID de A Encerre a transação de A na conexão de A
A terminou, mas B ainda informa PIDs bloqueadores Inspecione os bloqueadores restantes
B informa um tempo limite de bloqueio Reverta a transação explícita de B antes de tentar novamente
B retorna um erro de dependência Inspecione as dependências em vez de tratar isso como uma espera por bloqueio
B retorna imediatamente Confira se a transação do leitor pretendida ainda estava aberta

IF EXISTS trata o caso de uma tabela ausente, enquanto CASCADE controla a remoção de objetos dependentes. Nenhuma das opções controla a espera por bloqueio. Mantenha o comportamento padrão de dependências neste exercício; a sintaxe oficial de DROP TABLE não tem uma variante CONCURRENTLY. PostgreSQL: DROP TABLE

Como o node-postgres deve encerrar uma transação somente para leitura?

Com node-postgres, use um único cliente obtido do pool para BEGIN, todas as instruções da transação e seu COMMIT ou ROLLBACK final. A documentação oficial sobre transações adverte explicitamente contra usar pool.query() para uma transação, pois as instruções podem ser enviadas a clientes diferentes. node-postgres: transações

Trate o encerramento da transação e a liberação do cliente como duas etapas distintas da aplicação. client.release() devolve ao pool um cliente obtido dele; encerre a transação antes dessa chamada. Se a reversão falhar, a API do pool permite destruir o cliente passando um argumento verdadeiro para release(). node-postgres: API do pool

Para este programa completo, mantenha a tabela sintética criada no passo a passo. Use um ambiente de execução Node.js instalado que ofereça suporte a módulos ES e AggregateError, e instale pg em um diretório de projeto de trabalho:

npm install pg

Salve o seguinte como read-articles.mjs. Configure sua conexão para o mesmo banco de dados de teste local usado por psql. node-postgres oferece suporte às variáveis de ambiente de conexão do PostgreSQL, incluindo PGDATABASE. node-postgres: conexão

import pg from 'pg';

const { Pool } = pg;
const pool = new Pool({ connectionTimeoutMillis: 5000 });

async function main() {
  try {
    const client = await pool.connect();
    let discard = false;

    try {
      await client.query('BEGIN READ ONLY');
      const result = await client.query(
        'SELECT id, title FROM public.article_lock_demo ORDER BY id'
      );
      await client.query('COMMIT');
      console.table(result.rows);
    } catch (error) {
      try {
        await client.query('ROLLBACK');
      } catch (rollbackError) {
        discard = true;
        throw new AggregateError(
          [error, rollbackError],
          'Query and rollback failed'
        );
      }
      throw error;
    } finally {
      client.release(discard);
    }
  } finally {
    await pool.end();
  }
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
PGDATABASE=postgres node read-articles.mjs

O programa confirma a transação antes de exibir as linhas, reverte em caso de falha e libera o cliente nos dois casos. A sintaxe JavaScript foi verificada com Node.js; o passo a passo do banco de dados foi conferido com base na documentação, mas não foi executado aqui porque o PostgreSQL não estava disponível no ambiente de pesquisa.

Para uma leitura independente, pool.query() é o caminho prático documentado e evita obter um cliente manualmente. Escolha uma transação explícita quando sua tarefa exigir uma e mantenha o processamento de arquivos não relacionado ou a interação com a área de trabalho fora da duração dela. node-postgres: API do pool

Qual tempo limite se aplica a um leitor que não encerra a transação?

Use os tempos limite conforme o que está aguardando. lock_timeout limita a espera da exclusão pela aquisição de um bloqueio. statement_timeout limita a execução de instruções; portanto, não encerra uma transação simplesmente porque a aplicação para de enviar comandos depois de uma leitura bem-sucedida. idle_in_transaction_session_timeout encerra especificamente sessões que permanecem ociosas dentro de uma transação aberta. Estas são configurações documentadas do PostgreSQL 18; escolha durações adequadas ao trabalho esperado pela sua aplicação. PostgreSQL: padrões de conexão do cliente

Para esta tarefa, a verificação decisiva é se a transação do leitor terminou. Confirme o PID bloqueador, inspecione os bloqueios da tabela e encerre a transação na conexão que a iniciou. Em node-postgres, inclua esse comando final da transação nos fluxos normais de sucesso e erro para que uma leitura concluída não deixe a próxima exclusão da tabela aguardando.

Fontes