2012-06-18 16 views
7

Ho la seguente UPDATE query:come accelerare una query UPDATE lento

UPDATE Indexer.Pages SET LastError=NULL where LastError is not null; 

In questo momento, questa query richiede circa 93 minuti per completare. Mi piacerebbe trovare dei modi per rendere questo un po 'più veloce.

La tabella Indexer.Pages ha circa 506.000 righe, e circa 490.000 di essi contengono un valore per LastError, quindi dubito posso approfittare di tutti gli indici qui.

La tabella (quando non compressa) contiene circa 46 date di dati, tuttavia la maggior parte di tali dati si trova in un campo di testo denominato html. Credo che semplicemente il caricamento e lo scaricamento di molte pagine stiano causando il rallentamento. Un'idea sarebbe quella di creare un nuovo tavolo con solo il Id e il campo html e mantenere Indexer.Pages il più piccolo possibile. Tuttavia, testare questa teoria sarebbe una buona quantità di lavoro dal momento che in realtà non ho lo spazio su disco rigido per creare una copia del tavolo. Dovrei copiarlo su un'altra macchina, rilasciare la tabella, quindi copiare i dati che probabilmente richiederebbero tutta la sera.

Idee? Sto usando Postgres 9.0.0.

UPDATE:

Ecco lo schema:

CREATE TABLE indexer.pages 
(
    id uuid NOT NULL, 
    url character varying(1024) NOT NULL, 
    firstcrawled timestamp with time zone NOT NULL, 
    lastcrawled timestamp with time zone NOT NULL, 
    recipeid uuid, 
    html text NOT NULL, 
    lasterror character varying(1024), 
    missingings smallint, 
    CONSTRAINT pages_pkey PRIMARY KEY (id), 
    CONSTRAINT indexer_pages_uniqueurl UNIQUE (url) 
); 

Ho anche due indici:

CREATE INDEX idx_indexer_pages_missingings 
    ON indexer.pages 
    USING btree 
    (missingings) 
    WHERE missingings > 0; 

e

CREATE INDEX idx_indexer_pages_null 
    ON indexer.pages 
    USING btree 
    (recipeid) 
    WHERE NULL::boolean; 

Non ci sono trigger su questa tabella e vi è un'altra tabella con un vincolo FK su Pages.PageId.

+0

L'aggiornamento di 500.000 righe non dovrebbe richiedere 93 minuti. Presumo che ci sia qualcosa di altro coinvolto. Puoi mostrarci la definizione del tavolo? Anche la copia di 500.000 righe dovrebbe essere eseguita in un paio di minuti (se non in secondi usando 'COPY') non la" serata intera ". –

+3

A meno che l'html sia normalmente molto piccolo, verrà automaticamente memorizzato in una tabella TOAST separata dietro le quinte e non sarà significativo in questo aggiornamento. Eventuali trigger o definizioni di chiavi esterne potrebbero essere molto significative - ce ne sono? Esistono indici che fanno riferimento alla colonna LastError? Se è così, è probabile che sia un problema. Se è possibile organizzare l'aggiornamento in batch più piccoli (utilizzando intervalli di chiavi, ad esempio) e VACUUM tra un lotto e l'altro, si eviterà un aumento di volume della tabella. Infine, aggiorna: http://www.postgresql.org/support/versioning/ Una versione X.Y.0 non è un buon posto dove stare. – kgrittn

+0

Pubblicherò più informazioni sullo schema più tardi oggi. Tuttavia, al momento posso dire che non ci sono trigger, e questo schema è stato progettato per gli inserimenti veloci, quindi non c'è molto in termini di indici. –

risposta

6

Ciò che @kgrittn posted as comment è la migliore risposta finora.Sto semplicemente riempiendo i dettagli.

Prima di fare qualsiasi cosa altra cosa, si dovrebbe upgrade PostgreSQL to a current version, almeno per l'ultima release di sicurezza della vostra versione principale. See guidelines on the project.

Voglio anche sottolineare ciò che Kevin ha menzionato sugli indici che coinvolgono la colonna LastError. Normalmente, gli aggiornamenti HOT possono riciclare le righe morte su una pagina di dati e rendere gli UPDATE molto più veloci, eliminando in modo efficace (la maggior parte) la necessità di passare l'aspirapolvere. Related:

Se la colonna viene utilizzata in alcun indice in alcun modo, Aggiornamenti del sono disabilitate, perché si spezzerebbe l'indice (es). Se questo è il caso, dovresti essere in grado di velocizzare la query un sacco eliminando tutti questi indici prima di UPDATE e ricrearli in seguito.

In questo contesto sarebbe d'aiuto per eseguire più aggiornamenti più piccoli: Se ...
... la colonna aggiornata non è coinvolto in tutti gli indici (consentendo aggiornamenti HOT). ... lo UPDATE è facilmente diviso in più patch in più transazioni. ... le righe in quelle patch sono distribuite sul tavolo (fisicamente, non logicamente). ... non ci sono altre transazioni concorrenti che impediscono il riutilizzo di tuple morte.

Allora non avrebbe bisogno di VACCUUM tra più patch, in quanto gli aggiornamenti caldo può riutilizzare tuple morti direttamente - solo tuple morte dalla precedenti transazioni, non dallo stesso o da simultanei quelli. Potresti voler programmare un VACUUM alla fine dell'operazione, o semplicemente lasciare che l'auto-vacuuming faccia il suo lavoro.

Lo stesso potrebbe essere fatto con qualsiasi altro indice che non è necessario per il UPDATE - e a giudicare dai tuoi numeri il UPDATE non utilizzerà comunque un indice. Se si aggiornano ampie parti della tabella, la creazione di nuovi indici da zero è molto più rapida rispetto all'aggiornamento incrementale degli indici con ogni riga modificata.

Inoltre, l'aggiornamento non è in grado di violare i vincoli di chiave esterna . Potresti provare a eliminare & ricreare anche quelli. Ciò apre una fascia oraria in cui l'integrità referenziale non sarebbe applicata. Se l'integrità viene violata durante lo UPDATE, si verifica un errore durante il tentativo di ricreare l'FK. Se lo si fa tutto all'interno un transazione, transazioni concorrenti non arrivano mai a vedere la FK caduto, ma si prende un blocco di scrittura sul tavolo - come a cadere/indici ricreando o trigger)

Infine, disable & enable triggers che sono non necessario per l'aggiornamento.

Assicurati di fare tutto questo in un'unica transazione. Forse lo fanno in un numero di patch più piccoli, quindi non blocca le operazioni concorrenti troppo a lungo.

Quindi:

BEGIN; 
ALTER TABLE tbl DISABLE TRIGGER user; -- disable all self-made triggers 
-- DROP indexes (& fk constraints ?) 
-- UPDATE ... 
-- RECREATE indexes (& fk constraints ?) 
ALTER TABLE tbl ENABLE TRIGGER user; 
COMMIT; 

Non è possibile eseguire VACUUM all'interno di un blocco di transazione. Per documentation:

VACUUM non può essere eseguito all'interno di un blocco di transazione.

Si potrebbe dividere l'operazione in pochi grandi blocchi e correre in mezzo:

VACUUM ANALYZE tbl; 

Se non si ha a che fare con transazioni concorrenti si potrebbe (anche in modo più efficace):

ALTER TABLE tbl DISABLE TRIGGER user; -- disable all self-made triggers 
-- DROP indexes (& fk constraints ?) 

-- Multiple UPDATEs with logical slices of the table 
-- each slice in its own transaction. 
-- VACUUM ANALYZE tbl; -- optionally in between, or autovacuum kicks in 

-- RECREATE indexes (& fk constraints ?) 
ALTER TABLE tbl ENABLE TRIGGER user; 
+1

Punti secondari: i vuoti incrementali liberano i puntatori di tuple line, che non possono essere potati durante l'accesso normale. Quel * potrebbe * renderlo utile tra i passaggi di "AGGIORNAMENTO". Vi consiglio un 'FREEZE ANALYZE' VACUUM sul tavolo alla fine del processo se si prevede che tali righe rimanere (senza ulteriore' 'UPDATE' o DELETE') per lungo tempo; in caso contrario, i lavori di autovuotilazione che eseguono il blocco per congelare le tuple per impedire il wrapping di ID della transazione possono essere dolorosi. A questo punto potresti essere in grado di riscrivere le tuple con le informazioni sui bit di suggerimento e gli ID di transazione bloccati, risparmiando un piccolo sovraccarico. – kgrittn

+1

BTW, le persone sono attualmente affrontando alcuni dei problemi di prestazioni di lunga data con chiavi esterne - tra cui essere molto più intelligente schivare in testa se le particolari modifiche apportate non possono causare un problema di integrità referenziale. Se questa risposta è ancora in giro dopo 9.3 out, l'eliminazione delle chiavi esterne potrebbe essere molto meno importante. (E sì, intendo 9,3, che sarà probabilmente pubblicato nell'estate del 2013.) – kgrittn

0

La tua teoria è probabilmente corretta. Leggere la tabella completa (e quindi fare qualcosa) probabilmente sta causando il rallentamento.

Perché non si crea solo un'altra tabella con PageId e LastError? Inizializza con i dati nella tabella che hai ora (che dovrebbe richiedere meno di 93 minuti). Quindi, utilizzare LastError dalla nuova tabella.

A vostro piacimento, è possibile rimuovere LastError dalla tabella esistente.

A proposito, normalmente non consiglio di conservare due copie di una colonna in due tabelle separate. In questo caso, tuttavia, sembra di essere bloccato e di avere bisogno di un modo per procedere.

+0

Ottima idea di creare una nuova tabella con una minore quantità di dati, quindi eliminare le altre colonne dal primo. Quindi potrei semplicemente rinominare i tavoli quando ho finito. Inoltre, la nuova tabella potrebbe avere solo 'HtmlId' e' Html', e 'Indexer.Pages' potrebbe avere un riferimento a' HtmlId' - potrebbe essere un po 'più * normale *. –

1
UPDATE Indexer.Pages 
    SET LastError=NULL 
    ; 

La clausola WHERE non è necessario in quanto i campi NULL sono già NULL, in modo da non nuocere per impostare su null nuovo (non credo che questo potrebbe influenzare le prestazioni in modo significativo).

Dato il tuo numero_di_piedi = 500K e la dimensione della tabella = 46G, concludo che la tua media rowsize è 90 KB. È enorme. Forse potresti spostare {colonne inutilizzate, sparse} della tua tabella su altre tabelle?

+0

Tendo ad essere sulla stessa pagina, tuttavia @kgrittn ha accennato poiché queste righe sono tostate dalla tabella principale, non dovrebbe influire sull'aggiornamento perf perché non tocco quelle colonne. Sono curioso di sapere se questo è vero. Sfortunatamente, non potrò provarlo fino a tardo pomeriggio quando torno a casa. –

+1

@MikeChristensen: Oh, questo consiglio non è buono! Porterebbe a svuotare gli AGGIORNAMENTI rallentando la query. Devi assolutamente includere la clausola WHERE. Inoltre, poiché PostgreSQL utilizza le tabelle TOAST per i grandi valori, probabilmente non sarà di grande aiuto dividere la tabella. –

+0

Oltre al costo grezzo degli aggiornamenti di riga non necessari menzionati da @ErwinBrandstetter, si aumenterebbe inutilmente la tabella, causando un rallentamento di tutti gli accessi successivi fino a quando non si eseguirà una manutenzione aggressiva.Si noti che in PostgreSQL, un UPDATE crea una nuova versione della riga in una nuova posizione, lasciando la vecchia versione della riga per la pulizia successiva da un processo di vuoto. Un UPDATE senza clausola WHERE raddoppierà la dimensione della tua tabella di base. Questo è il motivo per cui stavo raccomandando più UPDATE più piccoli con VACUUM in mezzo, quindi lo spazio delle vecchie file può essere riutilizzato. – kgrittn