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

Come posso eseguire aggiornamenti non bloccanti di grandi dimensioni in PostgreSQL?

Colonna/Riga

... Non ho bisogno che l'integrità transazionale venga mantenuta durante l'intera operazione, perché so che la colonna che sto modificando non verrà scritta o letta durante l'aggiornamento.

Qualsiasi UPDATE nel modello MVCC di PostgreSQL scrive una nuova versione di l'intera riga . Se le transazioni simultanee cambiano qualsiasi colonna della stessa riga, sorgono problemi di concorrenza che richiedono tempo. Dettagli nel manuale. Conoscere la stessa colonna non sarà toccato da transazioni simultanee ne evita alcuni possibili complicazioni, ma non altre.

Indice

Per evitare di essere deviati in una discussione offtopic, assumiamo che tutti i valori di status per le 35 milioni di colonne siano attualmente impostati sullo stesso valore (non nullo), rendendo così un indice inutile.

Durante l'aggiornamento dell'intera tabella (o parti principali di esso) Postgres non usa mai un indice . Una scansione sequenziale è più veloce quando è necessario leggere tutte o la maggior parte delle righe. Al contrario:la manutenzione dell'indice comporta un costo aggiuntivo per l'UPDATE .

Prestazioni

Ad esempio, supponiamo che io abbia una tabella chiamata "ordini" con 35 milioni di righe e voglio farlo:

UPDATE orders SET status = null;

Capisco che stai puntando a una soluzione più generale (vedi sotto). Ma per affrontare la vera domanda chiesto:Questo può essere risolto in pochi millisecondi , indipendentemente dalle dimensioni della tabella:

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

Il manuale (fino a Postgres 10):

Quando viene aggiunta una colonna con ADD COLUMN , tutte le righe esistenti nella tabella vengono inizializzate con il valore predefinito della colonna (NULL se non DEFAULT clausola è specificata). Se non è presente DEFAULT clausola, si tratta semplicemente di una modifica dei metadati [...]

Il manuale (da Postgres 11):

Quando viene aggiunta una colonna con ADD COLUMN e un DEFAULT non volatile viene specificato, il valore predefinito viene valutato al momento dell'istruzione e il risultato viene archiviato nei metadati della tabella. Tale valore verrà utilizzato per la colonna per tutte le righe esistenti. Se nessun DEFAULT viene specificato, viene utilizzato NULL. In nessuno dei due casi è richiesta una riscrittura della tabella.

Aggiunta di una colonna con un DEFAULT volatile oppure la modifica del tipo di colonna esistente richiederà la scrittura dell'intera tabella e dei suoi indici. [...]

E:

La DROP COLUMN form non rimuove fisicamente la colonna, ma semplicemente la rende invisibile alle operazioni SQL. Le successive operazioni di inserimento e aggiornamento nella tabella memorizzeranno un valore nullo per la colonna. Pertanto, l'eliminazione di una colonna è rapida ma non ridurrà immediatamente le dimensioni su disco della tabella, poiché lo spazio occupato dalla colonna eliminata non viene recuperato. Lo spazio verrà recuperato nel tempo man mano che le righe esistenti vengono aggiornate.

Assicurati di non avere oggetti a seconda della colonna (vincoli di chiavi esterne, indici, viste, ...). Dovresti eliminarli / ricrearli. A parte ciò, piccole operazioni sulla tabella del catalogo di sistema pg_attribute Fai il lavoro. Richiede un blocco esclusivo sul tavolo che potrebbe essere un problema per un carico simultaneo elevato. (Come sottolinea Buurman nel suo commento.) A parte questo, l'operazione è una questione di millisecondi.

Se desideri mantenere una colonna predefinita, aggiungila di nuovo in un comando separato . Farlo nello stesso comando lo applica immediatamente a tutte le righe. Vedi:

  • Aggiungi nuova colonna senza blocco tabella?

Per applicare effettivamente l'impostazione predefinita, considera di farlo in batch:

  • PostgreSQL ottimizza l'aggiunta di colonne con valori predefiniti non NULL?

Soluzione generale

dblink è stato menzionato in un'altra risposta. Consente l'accesso a database Postgres "remoti" in connessioni separate implicite. Il database "remoto" può essere quello attuale, ottenendo così "transazioni autonome" :ciò che la funzione scrive nel db "remoto" viene eseguito e non può essere ripristinato.

Ciò consente di eseguire una singola funzione che aggiorna una tabella grande in parti più piccole e ogni parte viene impegnata separatamente. Evita l'accumulo di costi di transazione per un numero molto elevato di righe e, soprattutto, rilascia i blocchi dopo ogni parte. Ciò consente alle operazioni simultanee di procedere senza molto ritardo e rende meno probabili i deadlock.

Se non hai un accesso simultaneo, questo non è molto utile, tranne per evitare ROLLBACK dopo un'eccezione. Considera anche SAVEPOINT per quel caso.

Disclaimer

Prima di tutto, molte piccole transazioni sono in realtà più costose. Questo ha senso solo per i tavoli grandi . Il punto debole dipende da molti fattori.

Se non sei sicuro di quello che stai facendo:una singola transazione è il metodo sicuro . Affinché ciò funzioni correttamente, le operazioni simultanee sul tavolo devono essere al gioco. Ad esempio:scritture simultanee può spostare una riga in una partizione che presumibilmente è già elaborata. Oppure letture simultanee possono vedere stati intermedi incoerenti. Sei stato avvisato.

Istruzioni dettagliate

Il modulo aggiuntivo dblink deve essere prima installato:

  • Come usare (installare) dblink in PostgreSQL?

La configurazione della connessione con dblink dipende molto dalla configurazione del cluster di database e dalle politiche di sicurezza in atto. Può essere complicato. Risposta successiva correlata con ulteriori come connettersi con dblink :

  • Inserzioni persistenti in una UDF anche se la funzione si interrompe

Crea un FOREIGN SERVER e un USER MAPPING come indicato lì per semplificare e snellire la connessione (a meno che tu non ne disponga già).
Supponendo una serial PRIMARY KEY con o senza alcune lacune.

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;

Chiama:

SELECT f_update_in_steps();

Puoi parametrizzare qualsiasi parte in base alle tue esigenze:il nome della tabella, il nome della colonna, il valore, ... assicurati solo di disinfettare gli identificatori per evitare l'iniezione SQL:

  • Nome tabella come parametro di funzione PostgreSQL

Evita AGGIORNAMENTI vuoti:

  • Come faccio (o posso) SELEZIONARE DISTINCT su più colonne?