Misura la dimensione di una riga della tabella PostgreSQL


83

Ho una tabella PostgreSQL. select *è molto lento mentre select idè bello e veloce. Penso che potrebbe essere che la dimensione della fila sia molto grande e ci voglia un po 'di tempo per il trasporto, o potrebbe essere un altro fattore.

Ho bisogno di tutti i campi (o quasi tutti), quindi selezionare solo un sottoinsieme non è una soluzione rapida. La selezione dei campi che desidero è ancora lenta.

Ecco il mio schema di tabella meno i nomi:

integer                  | not null default nextval('core_page_id_seq'::regclass)
character varying(255)   | not null
character varying(64)    | not null
text                     | default '{}'::text
character varying(255)   | 
integer                  | not null default 0
text                     | default '{}'::text
text                     | 
timestamp with time zone | 
integer                  | 
timestamp with time zone | 
integer                  | 

La dimensione del campo di testo può essere di qualsiasi dimensione. Tuttavia, nel peggiore dei casi, non più di qualche chilobyte.

Domande

  1. C'è qualcosa in questo che urla "folle inefficiente"?
  2. C'è un modo per misurare le dimensioni della pagina nella riga di comando di Postgres per aiutarmi a eseguire il debug?

In realtà ... una delle colonne è di 11 MB. Questo lo spiegherà, penso. Quindi c'è un modo di fare length(*)piuttosto che solo length(field)? So che i caratteri non sono byte ma ho solo bisogno di un valore approssimativo.
Joe,

Risposte:


101

Q2: way to measure page size

PostgreSQL offre una serie di funzioni per la dimensione degli oggetti del database . Ho impacchettato i più interessanti in questa query e ho aggiunto alcune funzioni di accesso alle statistiche in basso. (Il modulo aggiuntivo pgstattuple fornisce ancora funzioni più utili.)

Questo mostrerà che metodi diversi per misurare la "dimensione di una riga" portano a risultati molto diversi. Tutto dipende esattamente da cosa vuoi misurare.

Questa query richiede Postgres 9.3 o successivo . Per le versioni precedenti vedi sotto.

Utilizzando VALUESun'espressione in una LATERALsottoquery , per evitare di sillabare i calcoli per ogni riga.

Sostituisci public.tbl(due volte) con il nome della tabella facoltativamente qualificato per ottenere una visione compatta delle statistiche raccolte sulla dimensione delle tue righe. Puoi avvolgerlo in una funzione plpgsql per un uso ripetuto, inserire il nome della tabella come parametro e usare EXECUTE...

SELECT l.metric, l.nr AS "bytes/ct"
     , CASE WHEN is_size THEN pg_size_pretty(nr) END AS bytes_pretty
     , CASE WHEN is_size THEN nr / NULLIF(x.ct, 0) END AS bytes_per_row
FROM  (
   SELECT min(tableoid)        AS tbl      -- = 'public.tbl'::regclass::oid
        , count(*)             AS ct
        , sum(length(t::text)) AS txt_len  -- length in characters
   FROM   public.tbl t                     -- provide table name *once*
   ) x
 , LATERAL (
   VALUES
      (true , 'core_relation_size'               , pg_relation_size(tbl))
    , (true , 'visibility_map'                   , pg_relation_size(tbl, 'vm'))
    , (true , 'free_space_map'                   , pg_relation_size(tbl, 'fsm'))
    , (true , 'table_size_incl_toast'            , pg_table_size(tbl))
    , (true , 'indexes_size'                     , pg_indexes_size(tbl))
    , (true , 'total_size_incl_toast_and_indexes', pg_total_relation_size(tbl))
    , (true , 'live_rows_in_text_representation' , txt_len)
    , (false, '------------------------------'   , NULL)
    , (false, 'row_count'                        , ct)
    , (false, 'live_tuples'                      , pg_stat_get_live_tuples(tbl))
    , (false, 'dead_tuples'                      , pg_stat_get_dead_tuples(tbl))
   ) l(is_size, metric, nr);

Risultato:

              metrica | byte / ct | bytes_pretty | bytes_per_row
----------------------------------- + ---------- + --- ----------- + ---------------
 core_relation_size | 44138496 | 42 MB | 91
 visibilità_map | 0 | 0 byte | 0
 free_space_map | 32768 | 32 kB | 0
 table_size_incl_toast | 44179456 | 42 MB | 91
 dimensione_indice | 33128448 | 32 MB | 68
 total_size_incl_toast_and_indexes | 77307904 | 74 MB | 159
 live_rows_in_text_representation | 29987360 | 29 MB | 62
 ------------------------------ | | |
 row_count | 483424 | |
 live_tuples | 483424 | |
 dead_tuples | 2677 | |

Per le versioni precedenti (Postgres 9.2 o precedenti):

WITH x AS (
   SELECT count(*)               AS ct
        , sum(length(t::text))   AS txt_len  -- length in characters
        , 'public.tbl'::regclass AS tbl      -- provide table name as string
   FROM   public.tbl t                       -- provide table name as name
   ), y AS (
   SELECT ARRAY [pg_relation_size(tbl)
               , pg_relation_size(tbl, 'vm')
               , pg_relation_size(tbl, 'fsm')
               , pg_table_size(tbl)
               , pg_indexes_size(tbl)
               , pg_total_relation_size(tbl)
               , txt_len
             ] AS val
        , ARRAY ['core_relation_size'
               , 'visibility_map'
               , 'free_space_map'
               , 'table_size_incl_toast'
               , 'indexes_size'
               , 'total_size_incl_toast_and_indexes'
               , 'live_rows_in_text_representation'
             ] AS name
   FROM   x
   )
SELECT unnest(name)                AS metric
     , unnest(val)                 AS "bytes/ct"
     , pg_size_pretty(unnest(val)) AS bytes_pretty
     , unnest(val) / NULLIF(ct, 0) AS bytes_per_row
FROM   x, y

UNION ALL SELECT '------------------------------', NULL, NULL, NULL
UNION ALL SELECT 'row_count', ct, NULL, NULL FROM x
UNION ALL SELECT 'live_tuples', pg_stat_get_live_tuples(tbl), NULL, NULL FROM x
UNION ALL SELECT 'dead_tuples', pg_stat_get_dead_tuples(tbl), NULL, NULL FROM x;

Stesso risultato

Q1: anything inefficient?

È possibile ottimizzare l' ordine delle colonne per risparmiare alcuni byte per riga, attualmente sprecato per il riempimento di allineamento:

integer                  | not null default nextval('core_page_id_seq'::regclass)
integer                  | not null default 0
character varying(255)   | not null
character varying(64)    | not null
text                     | default '{}'::text
character varying(255)   | 
text                     | default '{}'::text
text                     |
timestamp with time zone |
timestamp with time zone |
integer                  |
integer                  |

Ciò consente di risparmiare tra 8 e 18 byte per riga. Lo chiamo "colonna tetris" . Dettagli:

Considera anche:


Lo snippet pre 9.3 genera una divisione per zero se la tabella è vuota. In realtà volevo usare la versione 9.3+, ma ho scelto quella sbagliata per errore e ho dovuto passare qualche ora a ripararla ... Ora non posso perdere tutto quel tempo. Sostituisci , unnest(val) / ctcon , (LEAST(unnest(val), unnest(val) * ct)) / (ct - 1 + sign(ct))e non verrà lanciato. La logica è che, quando ctè 0, valsarà sostituito da 0e ctsarà sostituito da 1.
GuiRitter il

1
@GuiRitter: grazie per averci segnalato. Ho applicato una soluzione più semplice, però. Anche alcuni aggiornamenti generali mentre ci si trova - ma la query rimane la stessa.
Erwin Brandstetter,

35

Un'approssimazione della dimensione di una riga, inclusi i contenuti di TOAST , è facile da ottenere interrogando la lunghezza della rappresentazione TEXT dell'intera riga:

SELECT octet_length(t.*::text) FROM tablename AS t WHERE primary_key=:value;

Questa è una stretta approssimazione al numero di byte che verranno recuperati sul lato client durante l'esecuzione:

SELECT * FROM tablename WHERE primary_key=:value;

... supponendo che il chiamante della query richieda risultati in formato testo, che è ciò che fanno la maggior parte dei programmi (il formato binario è possibile, ma nella maggior parte dei casi non vale la pena).

La stessa tecnica potrebbe essere applicata per individuare le Nrighe "più grandi nel testo" di tablename:

SELECT primary_key, octet_length(t.*::text) FROM tablename AS t
   ORDER BY 2 DESC LIMIT :N;

Ottimo modo per ottenere rapidamente delle stime quando si lavora con big data (ad esempio la maggior parte delle dimensioni della riga si trova in colonne archiviate in toast di lunghezza variabile), buona idea!
fgblomqvist,

il risultato sono byte?
Akmal Salikhov,

14

Ci sono alcune cose che potrebbero succedere. In generale, dubito che la lunghezza sia il problema prossimale. Sospetto invece che tu abbia un problema relativo alla lunghezza.

Dici che i campi di testo possono arrivare a qualche k. Una riga non può superare gli 8k nella memoria principale ed è probabile che i campi di testo più grandi siano stati TOASTed o spostati dalla memoria principale a una memoria estesa in file separati. Questo rende la tua memoria principale più veloce (quindi seleziona id in realtà è più veloce perché meno pagine del disco per accedere) ma seleziona * diventa più lento perché c'è più I / O casuale.

Se le dimensioni della riga totale sono ancora ben al di sotto di 8 k, potresti provare a modificare le impostazioni di archiviazione. Vorrei tuttavia avvertire che è possibile che accadano cose brutte quando si inserisce un attributo di grandi dimensioni nella memoria principale, quindi è meglio non toccarlo se non è necessario e, in tal caso, impostare limiti appropriati tramite vincoli di controllo. Quindi il trasporto non è probabilmente l'unica cosa. Potrebbe essere la raccolta di molti, molti campi che richiedono letture casuali. Un numero elevato di letture casuali può anche causare errori nella cache e una grande quantità di memoria richiesta può richiedere che le cose si materializzino sul disco e un numero elevato di righe larghe, se è presente un join (e ce n'è uno se è coinvolto TOAST) può richiedere costi più elevati unisci schemi, ecc.

La prima cosa che vorrei guardare è selezionare meno righe e vedere se questo aiuta. Se funziona, potresti provare ad aggiungere anche più RAM al server, ma vorrei iniziare e vedere dove le prestazioni iniziano a diminuire a causa di modifiche al piano e mancano prima la cache.


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.