sql >> Database >  >> RDS >> PostgreSQL

Hoe voer ik grote niet-blokkerende updates uit in PostgreSQL?

Kolom/rij

... Ik hoef de transactie-integriteit niet te handhaven gedurende de hele operatie, omdat ik weet dat de kolom die ik aan het wijzigen ben niet zal worden geschreven of gelezen tijdens de update.

Elke UPDATE in PostgreSQL's MVCC-model schrijft een nieuwe versie van de hele rij . Als gelijktijdige transacties elke veranderen kolom van dezelfde rij, treden tijdrovende gelijktijdigheidsproblemen op. Details in de handleiding. Dezelfde kolom kennen wordt niet geraakt door gelijktijdige transacties vermijdt sommige mogelijke complicaties, maar andere niet.

Index

Laten we, om te voorkomen dat we worden afgeleid naar een offtopic discussie, aannemen dat alle statuswaarden voor de 35 miljoen kolommen momenteel zijn ingesteld op dezelfde (niet-null) waarde, waardoor een index onbruikbaar wordt.

Bij het bijwerken van de hele tabel (of grote delen ervan) Postgres gebruikt nooit een index . Een sequentiële scan is sneller wanneer alle of de meeste rijen moeten worden gelezen. Integendeel:Indexonderhoud betekent extra kosten voor de UPDATE .

Prestaties

Laten we bijvoorbeeld zeggen dat ik een tabel heb met de naam "orders" met 35 miljoen rijen, en ik wil dit doen:

UPDATE orders SET status = null;

Ik begrijp dat u streeft naar een meer algemene oplossing (zie hieronder). Maar om de eigenlijke vraag te beantwoorden gevraagd:Dit kan worden afgehandeld in een kwestie van milliseconden , ongeacht de tafelgrootte:

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

De handleiding (tot Postgres 10):

Wanneer een kolom wordt toegevoegd met ADD COLUMN , worden alle bestaande rijen in de tabel geïnitialiseerd met de standaardwaarde van de kolom (NULL indien geen DEFAULT clausule is vermeld). Als er geen DEFAULT . is clausule, dit is slechts een wijziging van de metagegevens [...]

De handleiding (sinds Postgres 11):

Wanneer een kolom wordt toegevoegd met ADD COLUMN en een niet-vluchtige DEFAULT is opgegeven, wordt de standaardwaarde geëvalueerd op het moment van de instructie en wordt het resultaat opgeslagen in de metagegevens van de tabel. Die waarde wordt gebruikt voor de kolom voor alle bestaande rijen. Indien geen DEFAULT is opgegeven, wordt NULL gebruikt. In geen van beide gevallen is het herschrijven van de tabel vereist.

Een kolom toevoegen met een vluchtige DEFAULT of het wijzigen van het type bestaande kolom vereist dat de hele tabel en zijn indexen worden herschreven. [...]

En:

De DROP COLUMN formulier verwijdert de kolom niet fysiek, maar maakt deze eenvoudigweg onzichtbaar voor SQL-bewerkingen. Daaropvolgende invoeg- en bijwerkbewerkingen in de tabel zullen een null-waarde voor de kolom opslaan. Het verwijderen van een kolom is dus snel, maar het zal niet onmiddellijk de schijfgrootte van uw tabel verkleinen, omdat de ruimte die door de verwijderde kolom wordt ingenomen niet wordt teruggewonnen. De ruimte wordt na verloop van tijd teruggewonnen als bestaande rijen worden bijgewerkt.

Zorg ervoor dat je geen objecten hebt die afhankelijk zijn van de kolom (buitenlandse sleutelbeperkingen, indices, views, ...). Je zou die moeten laten vallen / opnieuw maken. Afgezien daarvan, kleine bewerkingen op de systeemcatalogustabel pg_attribute doe het werk. Vereist een exclusief slot op de tafel, wat een probleem kan zijn voor zware gelijktijdige belasting. (Zoals Buurman benadrukt in zijn commentaar.) Afgezien daarvan is de operatie een kwestie van milliseconden.

Als je een kolomstandaard hebt die je wilt behouden, voeg deze dan in een aparte opdracht toe . Als u het in hetzelfde commando doet, wordt het onmiddellijk op alle rijen toegepast. Zie:

  • Nieuwe kolom toevoegen zonder tabelvergrendeling?

Om de standaard daadwerkelijk toe te passen, kunt u overwegen dit in batches te doen:

  • Optimaliseert PostgreSQL het toevoegen van kolommen met niet-NULL-standaardwaarden?

Algemene oplossing

dblink is genoemd in een ander antwoord. Het geeft toegang tot "externe" Postgres-databases in impliciete afzonderlijke verbindingen. De "externe" database kan de huidige zijn, waardoor "autonome transacties" worden bereikt :wat de functie schrijft in de "remote" db is vastgelegd en kan niet worden teruggedraaid.

Dit maakt het mogelijk om een ​​enkele functie uit te voeren die een grote tabel in kleinere delen bijwerkt en elk deel wordt afzonderlijk vastgelegd. Vermijdt het opbouwen van transactieoverhead voor zeer grote aantallen rijen en, nog belangrijker, geeft vergrendelingen vrij na elk onderdeel. Hierdoor kunnen gelijktijdige bewerkingen zonder veel vertraging worden uitgevoerd en worden impasses minder waarschijnlijk.

Als je geen gelijktijdige toegang hebt, is dit nauwelijks nuttig - behalve om ROLLBACK te vermijden na een uitzondering. Overweeg ook SAVEPOINT voor dat geval.

Disclaimer

Allereerst zijn veel kleine transacties juist duurder. Dit heeft alleen zin voor grote tafels . De goede plek hangt van veel factoren af.

Als u niet zeker weet wat u doet:een enkele transactie is de veilige methode . Om dit goed te laten werken, moeten gelijktijdige bewerkingen op de tafel meespelen. Bijvoorbeeld:gelijktijdige schrijft kan een rij verplaatsen naar een partitie die zogenaamd al is verwerkt. Of gelijktijdige uitlezingen kunnen inconsistente tussenstatussen zien. Je bent gewaarschuwd.

Stap-voor-stap instructies

De extra module dblink moet eerst worden geïnstalleerd:

  • Hoe dblink gebruiken (installeren) in PostgreSQL?

Het opzetten van de verbinding met dblink hangt sterk af van de instelling van uw DB-cluster en beveiligingsbeleid. Het kan lastig zijn. Gerelateerd later antwoord met meer hoe verbinding te maken met dblink :

  • Persistente invoegingen in een UDF, zelfs als de functie wordt afgebroken

Maak een FOREIGN SERVER en een USER MAPPING zoals daar geïnstrueerd om de verbinding te vereenvoudigen en te stroomlijnen (tenzij je er al een hebt).
Uitgaande van een serial PRIMARY KEY met of zonder enkele gaten.

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;

Bel:

SELECT f_update_in_steps();

U kunt elk onderdeel volgens uw behoeften parametriseren:de tabelnaam, kolomnaam, waarde, ... zorg er wel voor dat u identifiers opschoont om SQL-injectie te voorkomen:

  • Tabelnaam als een PostgreSQL-functieparameter

Vermijd lege UPDATEs:

  • Hoe kan (of kan ik) DISTINCT SELECTEREN op meerdere kolommen?


  1. Getallen opmaken als valuta in MySQL

  2. Hoe MySQL-verbindingen (max_connections) vergroten?

  3. Hoe current_time werkt in PostgreSQL

  4. Hoe MySQL te vermijden 'Deadlock gevonden bij het proberen te vergrendelen; probeer transactie opnieuw te starten'