En öppen skrivskyddad PostgreSQL-transaktion kan blockera DROP TABLE eftersom dess SELECT håller ett ACCESS SHARE-tabellås tills transaktionen avslutas. Borttagningen behöver ett motstridigt ACCESS EXCLUSIVE-lås och väntar därför, även efter att frågan har returnerat. PostgreSQL: Explicit låsning

Den här guiden visar hur du återskapar vänteläget från en Mac, identifierar den blockerande sessionen och avslutar transaktionen korrekt med node-postgres. Den använder endast syntetiska artikelrader. SQL-exemplen är avsedda för PostgreSQL 18, den version som den officiella aktuella dokumentationen som hämtades för den här artikeln gäller. Läs handboken för den version som din server använder innan du tillämpar versionsspecifika inställningar.

Vad innebär skrivskyddat läge för PostgreSQL-lås?

READ ONLY begränsar vilka kommandon en transaktion får köra. Det förbjuder till exempel ändringar i vanliga tabeller och kommandon som DROP TABLE. Det innebär inte att läsningar slipper tabellås. Läsaren och sessionen som försöker ta bort tabellen är separata transaktioner i det här scenariot. PostgreSQL: SET TRANSACTION

En vanlig SELECT skaffar automatiskt ACCESS SHARE på de tabeller som refereras. DROP TABLE kräver ACCESS EXCLUSIVE, vilket står i konflikt med det låset. Lås varar normalt tills transaktionen avslutas. Ett dokumenterat undantag är återställning till en savepoint som upprättades innan ett lås förvärvades. Exemplet nedan använder inga savepoints. PostgreSQL: Explicit låsning

Den viktiga skillnaden är mellan att en fråga slutförs och att en transaktion slutförs. Om du ser artikelrader i terminalen vet du att frågan returnerade. Kontrollera om den omgivande transaktionen också har avslutats. I psql är autocommit normalt aktiverat, men en explicit BEGIN håller transaktionen öppen tills du avslutar den. PostgreSQL: psql

Hur kan du återskapa den blockerade borttagningen på en Mac?

Använd en befintlig lokal testdatabas med PostgreSQL 18, och se till att psql redan finns tillgängligt i Terminal på din Mac. Öppna tre terminalflikar som är anslutna till samma databas. Kalla dem A, B och C. Det måste vara separata anslutningar; separata paneler i en redigerare innebär inte i sig att anslutningarna är separata.

Följande anslutningskommando förutsätter att din lokala testdatabas heter postgres. Byt vid behov ut namnet mot namnet på din befintliga testdatabas. Kör kommandot i varje flik. Alternativet -X förhindrar att en personlig startfil, psql, läses in. PostgreSQL: psql

psql -X -d postgres

Aktivera autocommit uttryckligen i varje anslutning och kontrollera serverversionen. Blocken nedan är en genomgång i ordning: senare block förutsätter att tidigare block har upprättat konfigurationen och sessionstillståndet.

\set AUTOCOMMIT on
SHOW server_version;

Skapa den syntetiska artikeltabellen

Kör konfigurationen en gång i flik C. Använd ett ledigt tabellnamn: om public.article_lock_demo redan finns ska du avbryta och välja ett annat namn, konsekvent genom alla exempel. Konfigurationen tar avsiktligt inte bort en befintlig tabell.

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');

Låt läsartransaktionen förbli öppen

Kör det här blocket i flik A och lämna anslutningen vid prompten. Gör ingen commit ännu.

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

Förvänta dig att transaction_read_only visar on, följt av de två syntetiska raderna. Läsningen är klar, men den explicita transaktionen är fortfarande öppen.

Försök ta bort tabellen från en annan anslutning

Kör följande block i flik B. Skicka DROP TABLE som sista sats, utan att lägga till något annat kommando.

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

Borttagningen bör vänta medan A är öppen. Gränsen på 60 sekunder är ett illustrativt exempel som ger dig tid att inspektera vänteläget; den är inte en inställning som rekommenderas för alla. lock_timeout avbryter en sats som väntar för länge på att få ett lås och tillämpas separat vid varje försök att förvärva ett lås. PostgreSQL: Standardvärden för klientanslutningar

Låt B:s explicita transaktion förbli öppen efter att borttagningen returnerat. Du återställer den senare, så att demonstrationstabellen finns kvar även om borttagningen lyckas. DROP TABLE tar bort tabellen och dess tillhörande index och begränsningar när transaktionen genomförs med commit. PostgreSQL: DROP TABLE

Hur bevisar du vilken session som blockerar borttagningen?

Kör nästa fråga i C medan B väntar. Den identifierar de två demonstrationsanslutningarna och frågar PostgreSQL vilka sessioner som blockerar varje anslutnings låsbegäran. pg_blocking_pids() returnerar en array med blockerande process-ID:n, eller en tom array om processen inte är blockerad. PostgreSQL: Systeminformationsfunktioner och -operatorer

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;

A bör visas som idle in transaction: den har en öppen transaktion men kör ingen fråga. B kan visas som active medan den väntar, med händelsetypen Lock. PostgreSQL dokumenterar exekveringstillstånd och väntande händelser som oberoende av varandra, så active ensamt visar inte att ett kommando gör framsteg. PostgreSQL: Systemet för kumulativ statistik

Granska sedan de faktiska tabellås som begärs. Kör detta i C innan du avslutar A:s transaktion.

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;

Det förväntade mönstret nedan är illustrativt och är inte fångad utdata från en körning. I pg_locks betyder granted = true att processen håller låset, medan false betyder att den väntar på låset. PostgreSQL: pg_locks

Session Låsläge granted Tolkning
article_reader_demo AccessShareLock true Läsaren håller sitt tabellås
article_drop_demo AccessExclusiveLock false Borttagningen väntar på sitt tabellås

Använd de två frågorna tillsammans som en diagnostisk sekvens: fastställ först blockeringsförhållandet och granska sedan låsen som hålls och begärs för den aktuella tabellen. Att en session är inaktiv räcker inte som bevis i sig; blockeringsförhållandet och låsraderna knyter den till just den här borttagningen.

Om B redan har nått tidsgränsen visas dess väntande låsbegäran inte längre. Kör ROLLBACK; i B, upprepa B:s transaktionsblock och inspektera snabbt medan A fortfarande är öppen.

Hur frigör du läsaren och slutför demonstrationen?

Avsluta den skrivskyddade transaktionen i A:

COMMIT;

Om B fortfarande väntar och A var dess enda blockerare kan borttagningen nu slutföras. Återställ genast demonstrationsborttagningen i B så att den syntetiska tabellen finns kvar:

ROLLBACK;

Om B nådde tidsgränsen innan du gjorde commit i A ska du först köra ROLLBACK; i B. När A har avslutats upprepar du B:s block och kör sedan ROLLBACK; igen när borttagningen har lyckats. Då hanteras båda möjliga utfallen utan att B:s transaktion lämnas oavslutad.

Följande beslutsstöd hjälper dig att skilja ett problem med transaktionens livslängd från andra förklaringar. Aktivitetsbeteckningarna följer PostgreSQL:s dokumenterade sessionstillstånd. PostgreSQL: Systemet för kumulativ statistik

Observation Nästa åtgärd
A är idle in transaction, och B:s blockerararray innehåller A:s PID Avsluta A:s transaktion från A:s anslutning
A har avslutats, men B rapporterar fortfarande blockerande PID:n Granska de återstående blockerarna
B rapporterar en låstidsgräns Återställ B:s explicita transaktion innan du försöker igen
B returnerar ett beroendefel Granska beroenden i stället för att behandla det som en låsväntan
B returnerar omedelbart Kontrollera om den avsedda läsartransaktionen fortfarande var öppen

IF EXISTS hanterar situationen när en tabell saknas, medan CASCADE styr borttagning av beroende objekt. Inget av dem är ett alternativ för låsväntan. Behåll standardbeteendet för beroenden i den här övningen. Den officiella syntaxen för DROP TABLE har ingen variant med CONCURRENTLY. PostgreSQL: DROP TABLE

Hur ska node-postgres avsluta en skrivskyddad transaktion?

Med node-postgres använder du en och samma utlånade klient för BEGIN, varje sats i transaktionen och den avslutande COMMIT eller ROLLBACK. Dokumentationen för transaktioner varnar uttryckligen för att använda pool.query() för en transaktion, eftersom satser kan skickas till olika klienter. node-postgres: Transaktioner

Behandla transaktionens avslut och frigörandet av klienten som två separata steg i applikationen. client.release() återför en utlånad klient till poolen; avsluta transaktionen innan du anropar den. Om återställningen misslyckas kan klienten förstöras via poolens API genom att skicka ett sanningsvärde till release(). node-postgres: Pool-API

För det här kompletta programmet behåller du den syntetiska tabellen från genomgången. Använd en installerad Node.js-körmiljö som har stöd för ES-moduler och AggregateError, och installera pg i en fungerande projektkatalog:

npm install pg

Spara följande som read-articles.mjs. Konfigurera anslutningen till samma lokala testdatabas som användes av psql. node-postgres stöder miljövariablerna för PostgreSQL-anslutning, inklusive PGDATABASE. node-postgres: Ansluta

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

Programmet genomför ändringarna med commit innan raderna visas, återställer transaktionen vid fel och frigör klienten i båda fallen. JavaScript-syntaxen kontrollerades med Node.js. Databaskörningen granskades mot dokumentationen men kördes inte här, eftersom PostgreSQL inte var tillgängligt i undersökningsmiljön.

För en enskild, fristående läsning är pool.query() den dokumenterade genvägen och du slipper låna en klient manuellt. Välj en explicit transaktion när uppgiften kräver det och håll orelaterad filbehandling eller interaktion med skrivbordet utanför transaktionens livslängd. node-postgres: Pool-API

Vilken tidsgräns hanterar en oavslutad läsartransaktion?

Använd tidsgränser efter vad som väntar. lock_timeout begränsar hur länge borttagningen väntar på att få ett lås. statement_timeout begränsar frågekörningen, så den avslutar inte en transaktion bara för att applikationen slutar skicka kommandon efter en lyckad läsning. idle_in_transaction_session_timeout avslutar specifikt sessioner som lämnats inaktiva i en öppen transaktion. Det här är dokumenterade inställningar för PostgreSQL 18. Välj tidslängder som passar det arbete applikationen förväntas utföra. PostgreSQL: Standardvärden för klientanslutningar

Den avgörande kontrollen för den här uppgiften är om läsarens transaktion har avslutats. Bekräfta det blockerande PID:t, granska tabellåsen och avsluta transaktionen på anslutningen som äger den. I node-postgres ska du låta det avslutande transaktionskommandot ingå i de vanliga kodvägarna för lyckade körningar och fel, så att en genomförd läsning inte får nästa tabellborttagning att vänta.

Källor