L'aggiunta di una colonna nullable alla tabella costa più di 10 minuti


11

Ho problemi ad aggiungere una nuova colonna su una tabella.
Ho provato a eseguirlo un paio di volte, ma dopo più di 10 minuti di esecuzione, ho deciso di annullare la query a causa del tempo di blocco.

ALTER TABLE mytable ADD mycolumn VARCHAR(50);

Informazioni utili:

  • Versione PostgreSQL: 9.1
  • Numero di file: ~ 250 K.
  • Numero di colonne: 38
  • Numero di colonne nullable: 32
  • Numero di vincoli: 5 (1 PK, 3 FK, 1 UNICO)
  • Numero di indici: 1
  • Tipo di sistema operativo: Debian Squeeze 64

Ho trovato informazioni interessanti sul modo in cui PostgreSQL gestisce le colonne nullable (tramite HeapTupleHeader).

La mia prima ipotesi è che, poiché questa tabella ha già 32 colonne nullable con 8 bit MAXALIGN, HeapTupleHeader ha una lunghezza di 4 byte (non verificato, e non so come farlo).

Pertanto, l'aggiunta di una nuova colonna nullable potrebbe richiedere un aggiornamento di HeapTupleHeader su ogni riga per aggiungere un nuovo 8 bit MAXALIGN, che potrebbe causare problemi di prestazioni.

Quindi ho provato a modificare una delle colonne nullable (che in realtà non è nullable nella realtà) al fine di ridurre a 31 il numero di colonne nullable, per verificare se la mia ipotesi potesse essere vera.

ALTER TABLE mytable ALTER myothercolumn SET NOT NULL;

Sfortunatamente, questa modifica richiede anche molto tempo, più di 5 minuti, quindi l'ho anche interrotta.

Hai un'idea di cosa potrebbe causare questo costo delle prestazioni?


1
Bene, posso dirti una parte di esso: alterare un tipo di colonna in un altro tipo che non è binario compatibile crea effettivamente una nuova colonna, copia i dati e imposta la vecchia colonna come rilasciata. Tuttavia, SET NOT NULLnon modifica il tipo, aggiunge solo un vincolo, ma il vincolo deve essere verificato rispetto alla tabella e ciò richiede una scansione completa della tabella. 9.4 migliora alcuni di questi casi prendendo blocchi più deboli, ma è ancora piuttosto pesante.
Craig Ringer,

1
Prima di sospettare che funzioni lentamente, devi assicurarti che ALTER TABLE non stia solo aspettando un blocco. Menzionalo nella domanda se hai controllato.
Daniel Vérité,

Grazie Craig e Daniel. Quando eseguo il comando alter, appare in pg_stat_activity con l'attesa "vero", suppongo che ciò significhi che aspetta un lucchetto !? È il buon modo per controllare? A proposito, prima di eseguire questa modifica, tutto va bene, ma pochi secondi dopo l'inizio, il numero di blocchi cresce

Prova la query su wiki.postgresql.org/wiki/Lock_dependency_information per una visione migliore. O hai transazioni persistenti che dimenticano di impegnare, o attività pesanti con questa tabella che le tiene sempre occupate.
Daniel Vérité,

Potrebbe essere una scelta migliore su dba.SE.
Erwin Brandstetter,

Risposte:


8

Ci sono un paio di equivoci qui:

La bitmap nullo è non parte dell'intestazione mucchio tupla. Per documentazione:

Esiste un'intestazione di dimensioni fisse (che occupa 23 byte sulla maggior parte delle macchine), seguita da una bitmap null opzionale ...

Le tue 32 colonne nullable sono insospettabili per due motivi:

  • La bitmap null viene aggiunta per riga e solo se nella riga è presente almeno un NULLvalore effettivo . Le colonne nullable non hanno impatto diretto, ma solo i NULLvalori effettivi . Se la bitmap null è allocata, viene sempre allocata completamente (tutto o niente). La dimensione effettiva della bitmap null è 1 bit per colonna, arrotondata per eccesso al byte successivo . Per codice souce corrente:

    #define BITMAPLEN(NATTS) (((int)(NATTS) + 7) / 8)
  • La bitmap null viene allocata dopo l'intestazione della tupla heap e seguita da un OID opzionale e quindi dai dati delle righe. L'inizio di un OID o dei dati di riga è indicato t_hoffnell'intestazione. Codice sorgente per commento :

    Nota che t_hoff deve essere un multiplo di MAXALIGN.

  • C'è un byte libero dopo l'intestazione della tupla heap, che occupa 23 byte. Pertanto, la bitmap null per righe fino a 8 colonne non ha costi aggiuntivi. Con la nona colonna nella tabella, t_hoffvengono avanzati altri MAXALIGN(in genere 8) byte per fornire altre 64 colonne. Quindi il prossimo bordo sarebbe a 72 colonne.

Per visualizzare le informazioni di controllo di un cluster di database PostgreSQL (incl. MAXALIGN), Esempio per un'installazione tipica di Postgres 9.3 su una macchina Debian:

    sudo /usr/lib/postgresql/9.3/bin/pg_controldata /var/lib/postgresql/9.3/main

Ho aggiornato le istruzioni nella relativa risposta che hai citato .

A parte questo, anche se la tua ALTER TABLEdichiarazione innesca un'intera riscrittura di una tabella (cosa che probabilmente fa, cambiando un tipo di dati), 250K non sono poi così tanti e sarebbero una questione di secondi su qualsiasi macchina a metà decente (a meno che le righe non siano insolitamente grandi) . 10 minuti o più indicano un problema completamente diverso. La tua dichiarazione è in attesa di ottenere un lucchetto sul tavolo, molto probabilmente.

Il numero crescente di voci pg_stat_activityindica transazioni più aperte: indica l'accesso simultaneo alla tabella (molto probabilmente) che deve attendere il completamento dell'operazione.

Alcuni scatti nel buio

Verifica l'eventuale presenza di un eccesso di tavolo, prova un metodo delicato VACUUM mytableo più aggressivo VACUUM FULL mytable, che potrebbe riscontrare gli stessi problemi di concorrenza, poiché questo modulo acquisisce anche un blocco esclusivo. Invece potresti provare pg_repack ...

Vorrei iniziare esaminando possibili problemi con indici, trigger, chiave esterna o altri vincoli, in particolare quelli che coinvolgono la colonna. Soprattutto un indice corrotto potrebbe essere coinvolto? Provali REINDEX TABLE mytable;o DROPtutti e aggiungili nuovamente dopo ALTER TABLE nella stessa transazione .

Prova a eseguire il comando di notte o quando non c'è molto carico.

Un metodo a forza bruta sarebbe quello di interrompere l'accesso al server, quindi riprovare:

Senza essere in grado di fissarlo, l'aggiornamento alla versione corrente o all'imminente 9.4 in particolare potrebbe aiutare. Sono stati apportati numerosi miglioramenti per i tavoli di grandi dimensioni e per i dettagli di blocco. Ma se c'è qualcosa di rotto nel tuo DB, dovresti probabilmente capirlo prima.


2
Quasi certamente è serrature. Ma, come test, puoi sempre creare una copia della tabella e provare a modificarla. Se ciò non richiede molto tempo, sai che non è la modifica vera e propria il problema.

Grazie per le spiegazioni Erwin. Penso che tu abbia ragione, sembra essere un problema di blocco. Quando controllo pg_stat_activity, vedo che il mio ALTER ha una "attesa" vera. Quello che non riesco a capire è il motivo per cui ALTER non riesce a ottenere il blocco sul tavolo, perché anche quando non riesco a trovare alcuna query in esecuzione, sembra che non riesca a ottenerlo. Ma non appena il mio ALTER inizia a funzionare, tutte le altre query attendono che finisca. Pertanto, l'attività sembra indicare che ALTER blocca tutte le altre query, ma indica anche che ALTER non ha ottenuto il blocco. Penso che ci sia qualcosa che non capisco bene !?

@MatthieuVerrecchia: hai provato il test suggerito da Richard?
Erwin Brandstetter,

1
Ho appena clonato il mio tavolo su uno nuovo (con pg_dump -> pg_sql). La nuova colonna viene aggiunta correttamente in 50 ms, il che conferma il problema del blocco. A proposito, ancora non capisco perché ALTER non può ottenere il blocco con attività di db davvero standard.

1
@ErwinBrandstetter Ho seguito i tuoi suggerimenti e ho provato un VUOTO, quindi un REINDEX. Anche il REINDEX stava bloccando, perché non era anche in grado di acquisire il blocco. Dopo alcune indagini, il problema era più semplice di quanto avremmo pensato. Rimaneva una settimana <IDLE> con una transazione aperta Il problema è stato risolto, grazie per tutto, le informazioni sono state molto utili.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.