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?