Kolonne/række
... Jeg har ikke brug for, at transaktionsintegriteten vedligeholdes gennem hele operationen, fordi jeg ved, at den kolonne, jeg ændrer, ikke bliver skrevet til eller læst under opdateringen.
Enhver OPDATERING
i PostgreSQL's MVCC-model skriver en ny version af hele rækken . Hvis samtidige transaktioner ændrer enhver kolonne i samme række, opstår der tidskrævende samtidighedsproblemer. Detaljer i manualen. At kende den samme kolonne vil ikke blive berørt af samtidige transaktioner undgår nogle mulige komplikationer, men ikke andre.
Indeks
For at undgå at blive omdirigeret til en offtopic diskussion, lad os antage, at alle statusværdierne for de 35 millioner kolonner i øjeblikket er sat til den samme (ikke-nul) værdi, hvilket gør et indeks ubrugeligt.
Når du opdaterer hele tabellen (eller større dele af det) Postgres bruger aldrig et indeks . En sekventiel scanning er hurtigere, når alle eller de fleste rækker skal læses. Tværtimod:Indeksvedligeholdelse betyder ekstra omkostninger for OPDATERING
.
Ydeevne
Lad os f.eks. sige, at jeg har en tabel kaldet "ordrer" med 35 millioner rækker, og jeg vil gøre dette:
UPDATE orders SET status = null;
Jeg forstår, at du sigter efter en mere generel løsning (se nedenfor). Men for at adressere det egentlige spørgsmål spurgt:Dette kan håndteres på nogle millisekunder , uanset bordstørrelse:
ALTER TABLE orders DROP column status
, ADD column status text;
Manualen (op til Postgres 10):
Når en kolonne tilføjes med ADD COLUMN
, alle eksisterende rækker i tabellen initialiseres med kolonnens standardværdi (NULL
hvis ingen DEFAULT
klausul er specificeret). Hvis der ikke er nogen DEFAULT
klausul, er dette blot en metadataændring [...]
Manualen (siden Postgres 11):
Når en kolonne tilføjes med ADD COLUMN
og en ikke-flygtig DEFAULT
er angivet, evalueres standarden på tidspunktet for erklæringen, og resultatet gemmes i tabellens metadata. Denne værdi vil blive brugt til kolonnen for alle eksisterende rækker. Hvis ingen DEFAULT
er angivet, bruges NULL. I ingen af tilfældene er en omskrivning af tabellen påkrævet.
Tilføjelse af en kolonne med en flygtig DEFAULT
eller ændring af typen af en eksisterende kolonne vil kræve, at hele tabellen og dens indekser skal skrives på ny. [...]
Og:
DROP COLUMN
form fjerner ikke kolonnen fysisk, men gør den blot usynlig for SQL-operationer. Efterfølgende indsættelses- og opdateringsoperationer i tabellen vil gemme en null-værdi for kolonnen. Det er derfor hurtigt at slette en kolonne, men det vil ikke umiddelbart reducere størrelsen på din tabel på disken, da pladsen optaget af den droppede kolonne ikke genvindes. Pladsen vil blive genvundet over tid, efterhånden som eksisterende rækker opdateres.
Sørg for, at du ikke har objekter afhængigt af kolonnen (fremmednøglebegrænsninger, indekser, visninger, ...). Du bliver nødt til at slippe / genskabe dem. Bortset fra det, små operationer på systemkatalogtabellen pg_attribute
gøre jobbet. Kræver en eksklusiv lås på bordet, hvilket kan være et problem ved kraftig samtidig belastning. (Som Buurman understreger i sin kommentar.) Bortset fra det er operationen et spørgsmål om millisekunder.
Hvis du har en kolonnestandard, du vil beholde, skal du tilføje den igen i en separat kommando . Hvis du gør det i den samme kommando, anvendes det på alle rækker med det samme. Se:
- Tilføj ny kolonne uden tabellås?
For faktisk at anvende standarden, overvej at gøre det i batches:
- Optimerer PostgreSQL tilføjelse af kolonner med ikke-NULL DEFAULT'er?
Generel løsning
dblink
er nævnt i et andet svar. Det giver adgang til "fjern" Postgres-databaser i implicitte separate forbindelser. Den "fjerne" database kan være den nuværende og derved opnå "autonome transaktioner" :hvad funktionen skriver i "remote" db er committet og kan ikke rulles tilbage.
Dette gør det muligt at køre en enkelt funktion, der opdaterer en stor tabel i mindre dele, og hver del er forpligtet separat. Undgår at opbygge transaktionsomkostninger for meget store antal rækker og, endnu vigtigere, frigiver låse efter hver del. Dette tillader samtidige operationer at fortsætte uden meget forsinkelse og gør dødvande mindre sandsynlige.
Hvis du ikke har samtidig adgang, er dette næppe nyttigt - undtagen for at undgå ROLLBACK
efter en undtagelse. Overvej også SAVEPOINT
for det tilfælde.
Ansvarsfraskrivelse
Først og fremmest er mange små transaktioner faktisk dyrere. Dette giver kun mening for store borde . Det søde sted afhænger af mange faktorer.
Hvis du ikke er sikker på, hvad du gør:en enkelt transaktion er den sikre metode . For at dette skal fungere korrekt, skal samtidige operationer på bordet spille med. For eksempel:samtidige skrivninger kan flytte en række til en partition, der angiveligt allerede er behandlet. Eller samtidige læsninger kan se inkonsistente mellemtilstande. Du er blevet advaret.
Trin-for-trin instruktioner
Det ekstra modul dblink skal installeres først:
- Hvordan bruger (installerer) dblink i PostgreSQL?
Opsætning af forbindelsen med dblink afhænger meget af opsætningen af din DB-klynge og sikkerhedspolitikker på plads. Det kan være tricky. Relateret senere svar med mere hvordan man forbinder med dblink :
- Vedholdende indsættelser i en UDF, selvom funktionen afbrydes
Opret en UDLANDS-SERVER
og en BRUGERKORTLÆGNING
som anvist der for at forenkle og strømline forbindelsen (medmindre du allerede har en).
Forudsat en seriel PRIMÆR NØGLE
med eller uden nogle huller.
CREATE OR REPLACE FUNCTION f_update_in_steps()
RETURNS void AS
$func$
DECLARE
_step int; -- size of step
_cur int; -- current ID (starting with minimum)
_max int; -- maximum ID
BEGIN
SELECT INTO _cur, _max min(order_id), max(order_id) FROM orders;
-- 100 slices (steps) hard coded
_step := ((_max - _cur) / 100) + 1; -- rounded, possibly a bit too small
-- +1 to avoid endless loop for 0
PERFORM dblink_connect('myserver'); -- your foreign server as instructed above
FOR i IN 0..200 LOOP -- 200 >> 100 to make sure we exceed _max
PERFORM dblink_exec(
$$UPDATE public.orders
SET status = 'foo'
WHERE order_id >= $$ || _cur || $$
AND order_id < $$ || _cur + _step || $$
AND status IS DISTINCT FROM 'foo'$$); -- avoid empty update
_cur := _cur + _step;
EXIT WHEN _cur > _max; -- stop when done (never loop till 200)
END LOOP;
PERFORM dblink_disconnect();
END
$func$ LANGUAGE plpgsql;
Ring til:
SELECT f_update_in_steps();
Du kan parametrere enhver del efter dine behov:tabelnavnet, kolonnenavnet, værdien, ... bare sørg for at rense identifikatorer for at undgå SQL-injektion:
- Tabelnavn som en PostgreSQL-funktionsparameter
Undgå tomme OPDATERINGER:
- Hvordan (eller kan jeg) VÆLGE DISTINCT på flere kolonner?