sql >> Database teknologi >  >> RDS >> PostgreSQL

Hvordan laver jeg store ikke-blokerende opdateringer i PostgreSQL?

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?


  1. MySQL Alter Stored Procedure

  2. Udførelsesrækkefølge for betingelser i SQL 'where'-sætning

  3. Installer Oracle Client fra kommandolinjen uden brugerinteraktion

  4. Hvor gemmer PostgreSQL databasen?