För att hålla en artikellista och dess antal konsekventa kör du båda läsningarna i en enda REPEATABLE READ READ ONLY-transaktion på samma node-postgres-klient. Den här handledningen visar mönstret och en kontroll med två sessioner som du kan köra på en Mac.
Varför kan en artikellista skilja sig från sitt antal?
Anta att ett skrivbordsskript hämtar publicerade artiklar i ämnet craft och sedan räknar matchande artiklar. Mellan dessa läsningar ändrar en annan session en artikels ämne. Den returnerade listan kan innehålla två artiklar medan antalet visar en.
PostgreSQLs standardisoleringsnivå, READ COMMITTED, ger varje vanlig SELECT en ögonblicksbild när den satsen börjar. Efterföljande läsningar kan därför se olika inlästa data, även inom en och samma transaktion. PostgreSQL: Transaction Isolation.
För den här uppgiften är det önskade resultatet en sammanhängande vy av artikelraderna. En senare ändring kan visas vid nästa körning. PostgreSQLs REPEATABLE READ håller efterföljande läsningar på den ögonblicksbild som etableras av transaktionens första sats som inte styr transaktionen; det räcker inte att bara köra BEGIN för att etablera den ögonblicksbilden. PostgreSQL: Transaction Isolation.
Implementeringen har två krav: välj isoleringsnivån innan du läser och kör alla relaterade frågor på samma klient. node-postgres kräver uttryckligen en enda klient för en transaktion och varnar för att använda pool.query() för dess satser. node-postgres: Transactions.
Förbered en liten artikeltestdatauppsättning på din Mac
Använd en lokal PostgreSQL-server som körs, kommandot psql och en Node.js-installation med npm som stöds. Kommandona nedan förutsätter att din befintliga lokala anslutning fungerar med databasen postgres. Om din arbetsdatabas har ett annat namn ersätter du det konsekvent. Installation av PostgreSQL och konfiguration av anslutningen är förutsättningar.
Öppna Terminal och skapa en projektmapp. Det dokumenterade node-postgres-paketet är pg. node-postgres: Welcome.
mkdir article-snapshot-demo
cd article-snapshot-demo
npm init -y
npm install pg
export PGDATABASE=postgres
psql -d postgres
Kör den här konfigurationen en gång vid prompten psql. Den skapar en särskild tabell som bara innehåller syntetiska artikelrader. De uttryckliga identifierarna gör också den senare övningen lätt att följa.
CREATE TABLE public.article_snapshot_demo (
id integer PRIMARY KEY,
title text NOT NULL,
topic text NOT NULL,
published boolean NOT NULL
);
INSERT INTO public.article_snapshot_demo
(id, title, topic, published)
VALUES
(1, 'Paper Shapes', 'craft', true),
(2, 'Color Studies', 'craft', true),
(3, 'Desk Layouts', 'design', true);
Om tabellen redan finns ska du stanna och granska den tidigare testdatauppsättningen i stället för att automatiskt ta bort eller ersätta den. Exemplen förutsätter exakt dessa tre startrader. Låt psql stå med \q innan du kör Node.js-skriptet.
Läs listan och antalet via en och samma incheckade klient
Spara följande kompletta program som read-articles.mjs i projektmappen. new Pool() använder de dokumenterade miljövariablerna för PostgreSQL-anslutningen, inklusive värdet PGDATABASE som exporterades ovan. node-postgres: Connecting.
Programmet hämtar en klient, startar transaktionen med dess egenskaper redan angivna, utför båda läsningarna och gör en commit innan det returnerar det sammansatta resultatet. PostgreSQL stöder dessa egenskaper direkt i BEGIN; transaktionen avslutas med COMMIT eller ROLLBACK. PostgreSQL: BEGIN.
import pg from 'pg';
const { Pool } = pg;
const pool = new Pool({ connectionTimeoutMillis: 5000 });
pool.on('error', (error) => {
console.error('Idle database client error:', error.message);
process.exitCode = 1;
});
async function readArticles(topic) {
const client = await pool.connect();
let began = false;
let discardClient = false;
try {
await client.query(
'BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY'
);
began = true;
const articles = await client.query(
`SELECT id, title, topic
FROM public.article_snapshot_demo
WHERE topic = $1 AND published = true
ORDER BY id`,
[topic]
);
const count = await client.query(
`SELECT count(*)::text AS total
FROM public.article_snapshot_demo
WHERE topic = $1 AND published = true`,
[topic]
);
await client.query('COMMIT');
began = false;
return {
topic,
articles: articles.rows,
total: count.rows[0].total
};
} catch (error) {
if (began) {
try {
await client.query('ROLLBACK');
} catch (rollbackError) {
discardClient = true;
throw new AggregateError(
[error, rollbackError],
'Article read failed; rollback also failed'
);
}
} else {
discardClient = true;
}
throw error;
} finally {
client.release(discardClient);
}
}
try {
const result = await readArticles('craft');
console.log(JSON.stringify(result, null, 2));
} catch (error) {
console.error(error);
process.exitCode = 1;
} finally {
await pool.end();
}
Kör programmet från samma Terminal-session:
node read-articles.mjs
Med den oförändrade testdatauppsättningen innehåller det förväntade resultatet artiklarna 1 och 2, där total är strängen "2". Antalet returneras uttryckligen som text, så det här exemplet kräver inte att det konverteras till ett JavaScript-tal. Båda frågorna använder samma ämnes- och publiceringsfilter; isolering kan inte rätta till olika predikat.
Rensningen är en del av det användbara mönstret. En misslyckad läsning utlöser en rollback, och om rollbacken misslyckas kastas klienten bort. node-postgres dokumenterar client.release(true) för att förstöra en incheckad klient och pool.end() för att stänga skriptets pool. node-postgres: Pool API.
Det här är ett fristående skript, så det avslutar sin pool efter en körning. I en befintlig applikation behåller du applikationens poollivscykel och placerar transaktionsfunktionen i den. Varje hjälpfunktion som deltar i dessa läsningar måste få den incheckade client; en hjälpfunktion som i tysthet anropar pool.query() bryter den gränsen. node-postgres: Transactions.
Verifiera ögonblicksbilden med två Terminal-sessioner
Ett snabbt skript kan bli klart innan en annan session hinner ändra något. Använd den här övningen med avsiktlig ordningsföljd för att tydliggöra den relevanta tidsordningen. Det här är SQL-steg som gäller för respektive session och använder den redan skapade tabellen, inte fristående konfigurationsprogram. De förväntade databasresultaten följer det dokumenterade isoleringsbeteendet; de presenteras inte som ett faktiskt kört PostgreSQL-test.
Öppna två Terminal-fönster och kör psql -d postgres i båda. Återställ testdatauppsättningens ämne i session B innan du startar läsaren. Vänta tills den här satsen är klar:
-- Session B: restore the fixture before beginning the exercise.
UPDATE public.article_snapshot_demo
SET topic = 'craft'
WHERE id = 2;
Starta läsartransaktionen i session A och hämta listan. Pausa när frågan har slutförts:
-- Session A: run these statements, then pause.
BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY;
SELECT id, title
FROM public.article_snapshot_demo
WHERE topic = 'craft' AND published = true
ORDER BY id;
Listan ska innehålla Paper Shapes och Color Studies. Växla nu till session B och gör en commit av en ämnesändring med den här fristående satsen. Utanför ett uttryckligt transaktionsblock gör PostgreSQL automatiskt en commit av en lyckad sats. PostgreSQL: BEGIN.
-- Session B: run while session A remains open.
UPDATE public.article_snapshot_demo
SET topic = 'design'
WHERE id = 2;
Gå tillbaka till session A och slutför den relaterade läsningen:
-- Session A: finish the original reader transaction.
SELECT count(*)::text AS total
FROM public.article_snapshot_demo
WHERE topic = 'craft' AND published = true;
COMMIT;
Det förväntade antalet är 2, vilket motsvarar session A:s tidigare lista. Därefter bör en ny körning av Node.js-skriptet bara returnera Paper Shapes och ett totalt antal på "1". Det gamla och det nya resultatet representerar varsin sammanhängande vy.
För en kontrollerad jämförelse upprepar du hela övningen, inklusive den inledande återställningen i session B, men startar session A med BEGIN ISOLATION LEVEL READ COMMITTED READ ONLY;. Behåll samma pauser och uppdateringsordning. Den förväntade första listan innehåller två artiklar, medan det senare antalet är 1. Den här jämförelsen gör transaktionsinställningen till den enda ändrade faktorn.
Välj rätt gräns för relaterade läsningar
Använd den här beslutsvägledningen när du anpassar exemplet. Bestäm först vilka utdata som måste stämma överens och välj sedan den minsta databasoperation som ger dig dem.
| Läsaruppgift | Lämpligt tillvägagångssätt | Villkor att bevara |
|---|---|---|
| Returnera en artikellista från en vanlig fråga | En fråga kan använda READ COMMITTED |
Ingen senare fråga behöver dela dess ögonblicksbild. |
| Returnera en lista och ett separat efterfrågat antal | En REPEATABLE READ READ ONLY-transaktion |
Använd en enda klient och matchande filter. |
| Hämta flera omgångar för ett sammanhängande resultat | Håll läsningarna inom samma transaktion | En ny transaktion etablerar en ny vy. |
| Samordna läsningar mellan separata databassessioner | Exportera och importera ögonblicksbilden uttryckligen | Oberoende transaktioner delar inte automatiskt en ögonblicksbild. |
För flera sessioner tillhandahåller PostgreSQL pg_export_snapshot() och SET TRANSACTION SNAPSHOT. Exportören måste förbli öppen tills de andra transaktionerna har importerat dess ögonblicksbild. En importerande transaktion måste använda REPEATABLE READ eller SERIALIZABLE och importera innan dess första fråga eller datamodifierande sats. Import i serialiserbart läge har ytterligare begränsningar som dokumenteras i kommandoreferensen. PostgreSQL: Snapshot Synchronization Functions, PostgreSQL: SET TRANSACTION.
För den här uppgiften med artikellista och antal undviker du extra samordning genom att behålla en enda klient. Slutför databasläsningarna och stäng transaktionen innan du väntar på att någon ska välja en annan åtgärd.
Vad lämnar den här isoleringsnivån olöst?
PostgreSQLs REPEATABLE READ förhindrar fantomläsningar, men serialiseringsavvikelser kan fortfarande förekomma. En stabil ögonblicksbild fastställer därför inte att samtidiga arbetsflöden betedde sig som om de hade körts ett i taget. Den låser inte heller sekvensvärden; sekvenser har särskilt transaktionsbeteende. PostgreSQL: Transaction Isolation.
READ ONLY förbjuder ändringar av vanliga tabeller, med dokumenterade undantag såsom vissa åtgärder på temporära tabeller. Det väljer inte i sig en stabil ögonblicksbild. Ange båda egenskaperna innan du läser: PostgreSQL låter dig inte ändra isoleringsnivån efter transaktionens första fråga eller datamodifierande sats. PostgreSQL: SET TRANSACTION.
När en rapport kräver starkare serialiserbar semantik stöder PostgreSQL SERIALIZABLE READ ONLY DEFERRABLE. Den kan vänta medan ögonblicksbilden hämtas och körs sedan utan att avbrytas på grund av serialiseringsfel. Den avvägningen skiljer sig från det vanliga kravet vid artikelläsning som visas här. PostgreSQL: SET TRANSACTION.
För artikellistan och antalet håller du implementeringsgränsen tydlig: hämta en klient, starta med de egenskaper som krävs, läs alla relaterade resultat, gör en commit och lämna tillbaka klienten. Återanvänd samma filter och publicera det sammansatta resultatet först när transaktionen har slutförts. Då får nästa åtgärd på skrivbordet en sammanhängande uppsättning artikeldata att arbeta med.
Källor
- Transaction Isolation — PostgreSQL Global Development Group.
- BEGIN — PostgreSQL Global Development Group.
- SET TRANSACTION — PostgreSQL Global Development Group.
- System Administration Functions: Snapshot Synchronization Functions — PostgreSQL Global Development Group.
- Transactions — node-postgres.
- Pool API — node-postgres.
- Connecting — node-postgres.
- Welcome — node-postgres.