it-swarm-eu.dev

Změřte velikost řádku tabulky PostgreSQL

Mám tabulku PostgreSQL. select * je velmi pomalé, zatímco select id je pěkné a rychlé. Myslím, že je možné, že velikost řádku je velmi velká a transport zabere chvíli, nebo to může být nějaký jiný faktor.

Potřebuji všechna pole (nebo téměř všechna), takže výběr pouze podmnožiny není rychlou opravou. Výběr polí, která chci, je stále pomalý.

Tady je moje tabulka schématu minus jména:

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                  | 

Velikost textového pole může být libovolná velikost. V nejhorším případě však ne více než několik kilobajtů.

Otázky

  1. Je na tom něco, co křičí „bláznivá neefektivní“?
  2. Existuje způsob, jak změřit velikost stránky na příkazovém řádku Postgres a pomoci mi to odladit?
94
Joe

Q2: way to measure page size

PostgreSQL poskytuje řadu funkcí velikosti objektu databáze . Sbalil jsem nejzajímavější z tohoto dotazu a přidal jsem nějaké Statistics Access Functions dole. (Další modul pgstattuple poskytuje ještě užitečnější funkce.)

To ukazuje, že různé metody měření „velikosti řádku“ vedou k velmi rozdílným výsledkům. Vše záleží na tom, co přesně chcete měřit.

Tento dotaz vyžaduje Postgres 9.3 nebo novější . Starší verze viz níže.

Pomocí výrazu VALUES v poddotazu LATERAL se vyhnete hláskování výpočtů pro každý řádek.

Nahradit public.tbl s vaším volitelným názvem tabulky podle schématu získáte kompaktní pohled na shromážděné statistiky velikosti řádků. Dalo by se to zabalit do funkce plpgsql pro opakované použití, zadejte parametr tabulky jako parametr a použijte 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);

Výsledek:

              metrický | bajty/ct | bytes_pretty | bytes_per_row 
 --------------------------------------- + -------- - + -------------- + --------------- 
 core_relation_size | 44138496 | 42 MB | 91 
 Visibility_map | 0 | 0 bajtů | 0 
 Free_space_map | 32768 | 32 kB | 0 
 Table_size_incl_toast | 44179456 | 42 MB | 91 
 Indexes_size | 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 | |

Pro starší verze ( Postgres 9.2 nebo starší ):

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;

Stejný výsledek.

Q1: anything inefficient?

Mohli byste optimalizovat pořadí sloupců , abyste uložili některé bajty na řádek, které jsou v současné době zbytečné na vyrovnání výplně:

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                  |

Tím se ušetří 8 až 18 bajtů na řádek. Říkám tomu "sloupec tetris". Podrobnosti:

Zvažte také:

105

Přibližování velikosti řádku, včetně obsahu TOAST 'ed, je snadné získat dotazováním délky reprezentace TEXTu celého řádku:

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

Toto je přibližná aproximace počtu bajtů, které budou načteny na straně klienta při provádění:

SELECT * FROM tablename WHERE primary_key=:value;

... za předpokladu, že volající dotazu požaduje výsledky v textovém formátu, což je to, co většina programů dělá (binární formát je možný, ale ve většině případů to nestojí za problém).

Stejnou techniku ​​lze použít k vyhledání řádků N „největšího textu“ v tablename:

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

Mohlo by se stát několik věcí. Obecně pochybuji, že délka je proximální problém. Mám podezření, že máte problém s délkou.

Říkáte, že textová pole mohou dosáhnout až několika k. Řádek nemůže přesáhnout 8 kB v hlavním úložišti a je pravděpodobné, že vaše větší textová pole byla TOASTed , nebo byla přesunuta z hlavního úložiště do rozšířeného úložiště v samostatných souborech. Tím se zrychlí vaše hlavní úložiště (takže výběr id je ve skutečnosti rychlejší, protože k přístupu na disk má méně stránek), ale výběr * se zpomalí, protože existuje více náhodných V/V.

Pokud jsou vaše celkové velikosti řádků stále dobře pod 8 kB, můžete zkusit změnit nastavení úložiště. Chtěl bych však upozornit, že při vložení nadměrného atributu do hlavního úložiště můžete dostat špatné věci, takže pokud je to nutné, nedotýkejte se, pokud to nemusíte, a pokud ano, stanovte příslušné limity prostřednictvím kontrolních omezení. Doprava tedy není jedinou věcí. Může to být řazení mnoha, mnoha polí, které vyžadují náhodné čtení. Velké množství náhodných čtení může také způsobit mezipaměti a velké množství požadované paměti může vyžadovat, aby se věci zhmotnily na disku a velké množství širokých řádků, pokud je přítomno spojení (a existuje jedno, pokud je zapojeno TOAST), může vyžadovat nákladnější připojit vzory atd.

První věc, na kterou bych se podíval, je vybrat méně řádků a zjistit, zda to pomůže. Pokud to funguje, můžete zkusit přidat další RAM také na server), ale já bych začal a uviděl, kde se výkon začne snižovat kvůli změnám plánu a mezipaměti chybí jako první.

14
Chris Travers

Pomocí výše uvedených funkce velikosti objektu databáze :

SELECT primary_key, pg_column_size(tablename.*) FROM tablename;
7