it-swarm-eu.dev

Pomalý dotaz na velké tabulce s GROUP BY a ORDER BY

Mám stůl s 7,2 milionu tuplů, který vypadá takto:

                               table public.methods
 column |          type         |                      attributes
--------+-----------------------+----------------------------------------------------
 id     | integer               | not null DEFAULT nextval('methodkey'::regclass)
 hash   | character varying(32) | not null
 string | character varying     | not null
 method | character varying     | not null
 file   | character varying     | not null
 type   | character varying     | not null
Indexes:
    "methods_pkey" PRIMARY KEY, btree (id)
    "methodhash" btree (hash)

Nyní chci vybrat některé hodnoty, ale dotaz je neuvěřitelně pomalý:

db=# explain 
    select hash, string, count(method) 
    from methods 
    where hash not in 
          (select hash from nostring) 
    group by hash, string 
    order by count(method) desc;
                                            QUERY PLAN
----------------------------------------------------------------------------------------
 Sort  (cost=160245190041.10..160245190962.07 rows=368391 width=182)
   Sort Key: (count(methods.method))
   ->  GroupAggregate  (cost=160245017241.77..160245057764.73 rows=368391 width=182)
       ->  Sort  (cost=160245017241.77..160245026451.53 rows=3683905 width=182)
             Sort Key: methods.hash, methods.string
             ->  Seq Scan on methods  (cost=0.00..160243305942.27 rows=3683905 width=182)
                   Filter: (NOT (SubPlan 1))
                   SubPlan 1
                   ->  Materialize  (cost=0.00..41071.54 rows=970636 width=33)
                     ->  Seq Scan on nostring  (cost=0.00..28634.36 rows=970636 width=33)

Sloupec hash je hash md5 string a má index. Takže si myslím, že můj problém je, že celá tabulka je tříděna podle id a ne podle hashe, takže chvíli to nejprve setřídíme a poté seskupíme?

Tabulka nostring obsahuje pouze seznam hashů, které nechci mít. Ale potřebuji obě tabulky, aby měly všechny hodnoty. Není tedy možné je odstranit.

další informace: žádný ze sloupců nemůže být null (opraveno jako v definici tabulky) a i'm pomocí postgresql 9.2.

14
reox

Hodnota LEFT JOIN V @ dezso odpověď by měla být dobrá. Index však bude stěží užitečný (sám o sobě), protože dotaz musí přesto přečíst celou tabulku - výjimkou jsou skenování pouze indexu v Postgresu 9.2+ a příznivé podmínky, viz níže.

SELECT m.hash, m.string, count(m.method) AS method_ct
FROM   methods m
LEFT   JOIN nostring n USING (hash)
WHERE  n.hash IS NULL
GROUP  BY m.hash, m.string 
ORDER  BY count(m.method) DESC;

Na dotaz spusťte EXPLAIN ANALYZE. Několikrát vyloučit efekty výplaty a šum. Porovnejte nejlepší výsledky.

Vytvořte index s více sloupci, který odpovídá vašemu dotazu:

CREATE INDEX methods_cluster_idx ON methods (hash, string, method);

Počkejte? Poté, co jsem řekl, index by nepomohl? Potřebujeme to k CLUSTER tabulce:

CLUSTER methods USING methods_cluster_idx;
ANALYZE methods;

Znovu spusťte EXPLAIN ANALYZE. Rychleji? To by mělo být.

CLUSTER je jednorázová operace, která přepíše celou tabulku v pořadí podle použitého indexu. Je to také efektivně VACUUM FULL. Pokud si chcete být jisti, spustili byste předběžný test pouze s VACUUM FULL, Abyste zjistili, co lze k tomu připsat.

Pokud vaše tabulka vidí mnoho operací zápisu, účinek se časem sníží. Naplánujte CLUSTER mimo pracovní dobu, chcete-li efekt obnovit. Jemné doladění závisí na konkrétním případu použití. Manuál o CLUSTER.

CLUSTER je poměrně hrubý nástroj, potřebuje exkluzivní zámek na stole. Pokud si to nemůžete dovolit, zvažte pg_repack , který může udělat totéž bez výhradního zámku. Více v této pozdější odpovědi:


Pokud je procento NULL hodnot ve sloupci method vysoké (více než ~ 20 procent, v závislosti na u skutečných velikostí řádků) by měl částečný index pomoci:

CREATE INDEX methods_foo_idx ON methods (hash, string)
WHERE method IS NOT NULL;

(Vaše pozdější aktualizace ukazuje, že vaše sloupce jsou NOT NULL, Takže se nepoužijí.)

Pokud používáte PostgreSQL 9.2 nebo novější (jako @deszo komentoval ) uvedené indexy mohou být užitečné bez CLUSTER, pokud plánovač může použít skenování pouze indexů . Použitelné pouze za příznivých podmínek: Indexem nesmí být pokryty žádné operace zápisu, které by ovlivnily mapu viditelnosti od posledního VACUUM a všech sloupců v dotazu. V podstatě to mohou používat tabulky jen pro čtení, zatímco silně psané tabulky jsou omezené. Více podrobností v Postgres Wiki.

Výše uvedený částečný index by v tomto případě mohl být ještě užitečnější.

Pokud , na druhé straně jsou ve sloupci žádné NULL hodnoty method, měli byste
1..) Definujte NOT NULL A
2..) Použijte count(*) namísto count(method), což je o něco rychlejší a to samé při absenci NULL hodnot.

Pokud musíte tento dotaz volat často a tabulka je pouze pro čtení, vytvořte MATERIALIZED VIEW .


Exotický jemný bod: Tabulka se jmenuje nostring, zdá se však, že obsahuje hash. Vyloučením hashů namísto řetězců existuje šance, že vyloučíte více řetězců, než bylo zamýšleno. Extrémně nepravděpodobné, ale možné.

18
Erwin Brandstetter

Vítejte v DBA.SE!

Můžete zkusit přeformulovat svůj dotaz takto:

SELECT m.hash, string, count(method) 
FROM 
    methods m
    LEFT JOIN nostring n ON m.hash = n.hash
WHERE n.hash IS NULL
GROUP BY hash, string 
ORDER BY count(method) DESC;

nebo jiná možnost:

SELECT m.hash, string, count(method) 
FROM 
    methods m
WHERE NOT EXISTS (SELECT hash FROM nostring WHERE hash = m.hash)
GROUP BY hash, string 
ORDER BY count(method) DESC;

NOT IN je typický dřez výkonu, protože je obtížné s ním používat index.

To může být dále vylepšeno indexy. Index na nostring.hash vypadá užitečné. Ale nejprve: co teď dostanete? (Bylo by lepší vidět výstup EXPLAIN ANALYZE vzhledem k tomu, že samotné náklady neříkají dobu, kterou operace trvalo.)

5
dezso

Protože hash je md5, můžete se jej pravděpodobně pokusit převést na číslo: můžete jej uložit jako číslo nebo vytvořit funkční index, který vypočítá toto číslo v neměnné funkci.

Ostatní lidé již vytvořili funkci pl/pgsql, která převádí (část) hodnoty md5 z textu na řetězec. Viz https://stackoverflow.com/questions/9809381/hashing-a-string-to-a-numeric-value-in-postgressql pro příklad

Věřím, že při skenování indexu trávíte opravdu hodně času porovnáváním řetězců. Pokud se vám podaří tuto hodnotu uložit jako číslo, pak by to mělo být opravdu opravdu rychlejší.

1
eppesuig

Hodně jsem narazil na tento problém, a objevil jednoduchý 2-dílný trik.

  1. Vytvořte index podřetězce na hodnotě hash: (7 je obvykle dobrá délka)

    create index methods_idx_hash_substring ON methods(substring(hash,1,7))

  2. Nechte vaše hledání/spojení zahrnovat shodu podřetězců, takže plánovač dotazů je naznačen, aby používal index:

    starý: WHERE hash = :kwarg

    nový: WHERE (hash = :kwarg) AND (substring(hash,1,7) = substring(:kwarg,1,7))

Také byste měli mít index na surovém hash.

výsledkem (obvykle) je, že plánovač nejprve konzultuje index podřetězce a vyřadí většinu řádků. pak odpovídá hashe 32 znaků s odpovídajícím indexem (nebo tabulkou). tento přístup pro mě snížil 800ms dotazů na 4.

0
Jonathan Vanasco