sql >> Databasteknik >  >> RDS >> PostgreSQL

Hur gör jag stora icke-blockerande uppdateringar i PostgreSQL?

Kolumn/rad

... Jag behöver inte upprätthålla transaktionsintegriteten under hela operationen, eftersom jag vet att kolumnen jag ändrar inte kommer att skrivas till eller läsas under uppdateringen.

Valfri UPDATE i PostgreSQL:s MVCC-modell skriver en ny version av hela raden . Om samtidiga transaktioner ändras någon kolumn i samma rad, uppstår tidskrävande samtidighetsproblem. Detaljer i manualen. Att känna till samma kolumn kommer inte att beröras av samtidiga transaktioner undviker en del möjliga komplikationer, men inte andra.

Index

För att undvika att bli omdirigerad till en offtopic diskussion, låt oss anta att alla statusvärdena för de 35 miljoner kolumnerna för närvarande är inställda på samma (icke-null) värde, vilket gör ett index värdelöst.

När du uppdaterar hela tabellen (eller större delar av det) Postgres använder aldrig ett index . En sekventiell skanning är snabbare när alla eller de flesta rader måste läsas. Tvärtom:Indexunderhåll innebär extra kostnad för UPDATE .

Prestanda

Låt oss till exempel säga att jag har en tabell som heter "order" med 35 miljoner rader, och jag vill göra detta:

UPDATE orders SET status = null;

Jag förstår att du siktar på en mer generell lösning (se nedan). Men för att ta itu med den faktiska frågan frågade:Detta kan hanteras på enkel millisekunder , oavsett tabellstorlek:

ALTER TABLE orders DROP column status
                 , ADD  column status text;

Manualen (upp till Postgres 10):

När en kolumn läggs till med ADD COLUMN , alla befintliga rader i tabellen initieras med kolumnens standardvärde (NULL om ingen DEFAULT klausul specificeras). Om det inte finns någon DEFAULT klausul är detta bara en metadataändring [...]

Manualen (sedan Postgres 11):

När en kolumn läggs till med ADD COLUMN och en icke-flyktig DEFAULT specificeras, utvärderas standardvärdet vid tidpunkten för uttalandet och resultatet lagras i tabellens metadata. Det värdet kommer att användas för kolumnen för alla befintliga rader. Om ingen DEFAULT anges, används NULL. I inget av fallen krävs en omskrivning av tabellen.

Lägga till en kolumn med en flyktig DEFAULT eller ändra typen av en befintlig kolumn kommer att kräva att hela tabellen och dess index skrivs om. [...]

Och:

DROP COLUMN form tar inte bort kolumnen fysiskt, utan gör den helt enkelt osynlig för SQL-operationer. Efterföljande infognings- och uppdateringsoperationer i tabellen kommer att lagra ett nollvärde för kolumnen. Det går därför snabbt att släppa en kolumn, men det kommer inte omedelbart att minska storleken på din tabell på disken, eftersom utrymmet som upptas av den tappade kolumnen inte återtas. Utrymmet kommer att återtas med tiden eftersom befintliga rader uppdateras.

Se till att du inte har objekt beroende på kolumnen (restriktioner för främmande nyckel, index, vyer, ...). Du skulle behöva släppa / återskapa dem. Utom det, små operationer på systemkatalogtabellen pg_attribute gör jobbet. Kräver ett exklusivt lås på bordet vilket kan vara ett problem vid kraftig samtidig belastning. (Som Buurman betonar i sin kommentar.) Bortsett från det är operationen en fråga om millisekunder.

Om du har en standardkolumn du vill behålla, lägg till den igen i ett separat kommando . Om du gör det med samma kommando tillämpas det på alla rader omedelbart. Se:

  • Lägg till ny kolumn utan tabelllås?

För att faktiskt tillämpa standarden, överväg att göra det i omgångar:

  • Optimerar PostgreSQL att lägga till kolumner med standardvärden som inte är NULL?

Allmän lösning

dblink har nämnts i ett annat svar. Den tillåter åtkomst till "fjärr" Postgres-databaser i implicita separata anslutningar. "Fjärrdatabasen" kan vara den aktuella, och därigenom uppnå "autonoma transaktioner" :vad funktionen skriver i "fjärr" db är committed och kan inte återställas.

Detta gör det möjligt att köra en enda funktion som uppdaterar en stor tabell i mindre delar och varje del bestäms separat. Undviker att bygga upp transaktionskostnader för mycket stora antal rader och, ännu viktigare, släpper lås efter varje del. Detta tillåter samtidiga operationer att fortsätta utan större fördröjning och gör dödläge mindre sannolikt.

Om du inte har samtidig åtkomst är detta knappast användbart - förutom för att undvika ROLLBACK efter ett undantag. Tänk också på SAVEPOINT för det fallet.

Ansvarsfriskrivning

Först och främst är massor av små transaktioner faktiskt dyrare. Detta är bara vettigt för stora bord . Den söta platsen beror på många faktorer.

Om du inte är säker på vad du gör:en enda transaktion är den säkra metoden . För att detta ska fungera korrekt måste samtidiga operationer på bordet spela med. Till exempel:samtidiga skrivningar kan flytta en rad till en partition som förmodligen redan har bearbetats. Eller samtidigt kan läsningar se inkonsekventa mellanliggande tillstånd. Du har blivit varnad.

Steg-för-steg-instruktioner

Tilläggsmodulen dblink måste installeras först:

  • Hur man använder (installerar) dblink i PostgreSQL?

Att konfigurera anslutningen med dblink beror mycket på konfigurationen av ditt DB-kluster och säkerhetspolicyer på plats. Det kan vara knepigt. Relaterat senare svar med mer hur man ansluter med dblink :

  • Ihållande infogning i en UDF även om funktionen avbryts

Skapa en FOREIGN SERVER och en USER MAPPING enligt instruktionerna där för att förenkla och effektivisera anslutningen (såvida du inte redan har en).
Antar en serial PRIMARY KEY med eller utan några luckor.

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:

SELECT f_update_in_steps();

Du kan parametrisera vilken del som helst efter dina behov:tabellnamnet, kolumnnamnet, värdet, ... se bara till att rensa identifierare för att undvika SQL-injektion:

  • Tabellnamn som en PostgreSQL-funktionsparameter

Undvik tomma UPPDATERINGAR:

  • Hur väljer jag (eller kan jag) DISTINCT på flera kolumner?


  1. Personsökning med Oracle och sql-server och generisk personsökningsmetod

  2. Förstå vad sp_updatestats verkligen uppdaterar

  3. Hur DATE_ADD() fungerar i MariaDB

  4. Oracle:Beräkna tidsskillnaden i TT:MM:SS mellan 2 datum