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?