Proč?
Důvod je toto:
Rychlý dotaz:
-> Hash Left Join (cost=1378.60..2467.48 rows=15 width=79) (actual time=41.759..85.037 rows=1129 loops=1) ... Filter: (unaccent(((((COALESCE(p.abrev, ''::character varying))::text || ' ('::text) || (COALESCE(p.prenome, ''::character varying))::text) || ')'::text)) ~~* (...)
Pomalý dotaz:
-> Hash Left Join (cost=1378.60..2467.48 rows=1 width=79) (actual time=35.084..80.209 rows=1129 loops=1) ... Filter: (unaccent(((((COALESCE(p.abrev, ''::character varying))::text || ' ('::text) || (COALESCE(p.prenome, ''::character varying))::text) || ')'::text)) ~~* unacc (...)
Rozšíření vyhledávacího vzoru o další znak způsobí, že Postgres předpokládá ještě méně zásahů. (Obvykle se jedná o rozumný odhad.) Postgres zjevně nemá dostatečně přesné statistiky (žádné, ve skutečnosti pokračujte ve čtení), abyste očekávali stejný počet zásahů, jaký skutečně získáte.
To způsobí přechod na jiný plán dotazů, který je pro aktuální ještě méně optimální počet zobrazení řádky=1129
.
Řešení
Za předpokladu, že aktuální Postgres 9.5 nebyl deklarován.
Jedním ze způsobů, jak situaci zlepšit, je vytvořit index výrazů na výraz v predikátu. Díky tomu Postgres shromažďuje statistiky pro skutečný výraz, což může pomoci dotazu i když samotný index není pro dotaz použit . Bez indexu nejsou žádné statistiky za výraz vůbec. A pokud se to udělá správně, index lze použít pro dotaz, je to ještě mnohem lepší. Existuje alevíce problémů s vaším aktuálním výrazem:
unaccent(TEXT(coalesce(p.abrev,'')||' ('||coalesce(p.prenome,'')||')')) ilike unaccent('%vicen%' )
Zvažte tento aktualizovaný dotaz na základě některých předpokladů o vašich nezveřejněných definicích tabulek:
SELECT e.id
, (SELECT count(*) FROM imgitem
WHERE tabid = e.id AND tab = 'esp') AS imgs -- count(*) is faster
, e.ano, e.mes, e.dia
, e.ano::text || to_char(e.mes2, 'FM"-"00')
|| to_char(e.dia, 'FM"-"00') AS data
, pl.pltag, e.inpa, e.det, d.ano anodet
, format('%s (%s)', p.abrev, p.prenome) AS determinador
, d.tax
, coalesce(v.val,v.valf) || ' ' || vu.unit AS altura
, coalesce(v1.val,v1.valf) || ' ' || vu1.unit AS dap
, d.fam, tf.nome família, d.gen, tg.nome AS gênero, d.sp
, ts.nome AS espécie, d.inf, e.loc, l.nome localidade, e.lat, e.lon
FROM pess p -- reorder!
JOIN det d ON d.detby = p.id -- INNER JOIN !
LEFT JOIN tax tf ON tf.oldfam = d.fam
LEFT JOIN tax tg ON tg.oldgen = d.gen
LEFT JOIN tax ts ON ts.oldsp = d.sp
LEFT JOIN tax ti ON ti.oldinf = d.inf -- unused, see @joop's comment
LEFT JOIN esp e ON e.det = d.id
LEFT JOIN loc l ON l.id = e.loc
LEFT JOIN var v ON v.esp = e.id AND v.key = 265
LEFT JOIN varunit vu ON vu.id = v.unit
LEFT JOIN var v1 ON v1.esp = e.id AND v1.key = 264
LEFT JOIN varunit vu1 ON vu1.id = v1.unit
LEFT JOIN pl ON pl.id = e.pl
WHERE f_unaccent(p.abrev) ILIKE f_unaccent('%' || 'vicenti' || '%') OR
f_unaccent(p.prenome) ILIKE f_unaccent('%' || 'vicenti' || '%');
Hlavní body
Proč f_unaccent()
? Protože unaccent()
nelze indexovat. Přečtěte si toto:
Použil jsem tam uvedenou funkci, abych umožnil následující (doporučeno!) vícesloupcový funkční trigram GIN index :
CREATE INDEX pess_unaccent_nome_trgm_idx ON pess
USING gin (f_unaccent(pess) gin_trgm_ops, f_unaccent(prenome) gin_trgm_ops);
Pokud nejste obeznámeni s indexy trigramů, přečtěte si nejprve toto:
A možná:
Ujistěte se, že používáte nejnovější verzi Postgres (aktuálně 9.5). Došlo k podstatným vylepšením indexů GIN. A budou vás zajímat vylepšení v pg_trgm 1.2, jehož vydání je naplánováno na nadcházející Postgres 9.6:
Připravená prohlášení jsou běžným způsobem provádění dotazů s parametry (zejména s textem z uživatelského vstupu). Postgres musí najít plán, který nejlépe funguje pro daný parametr. Přidejte zástupné znaky jako konstanty na hledaný výraz takto:
f_unaccent(p.abrev) ILIKE f_unaccent('%' || 'vicenti' || '%')
('vicenti'
Postgres tedy ví, že máme co do činění se vzorem, který není ukotven ani vlevo, ani vpravo – což by umožňovalo různé strategie. Související odpověď s dalšími podrobnostmi:
Nebo možná přeplánujte dotaz pro každý hledaný výraz (možná pomocí dynamického SQL ve funkci). Ujistěte se však, že čas na plánování nesníží žádné možné zvýšení výkonu.
WHERE
podmínka na sloupcích v pess
odporuje . Postgres je nucen to převést na LEFT JOIN
INNER JOIN
. A co je horší, spojení přichází pozdě ve stromu spojení. A protože Postgres nemůže změnit pořadí vašich spojení (viz níže), může se to velmi prodražit. Přesuňte tabulku na první pozici v OD
doložka k předčasnému odstranění řádků. Následuje LEFT JOIN
s nevylučují žádné řádky podle definice. Ale s takovým množstvím tabulek je důležité přesouvat spojení, která se mohou násobit řádků až do konce.
Připojujete se ke 13 stolům, 12 z nich pomocí LEFT JOIN
což ponechává 12!
možné kombinace - nebo 11! * 2!
pokud vezmeme jeden LEFT JOIN
v úvahu, že je to skutečně INNER JOIN
. To je taky mnoho pro Postgres k vyhodnocení všech možných permutací pro nejlepší plán dotazů. Přečtěte si o join_collapse_limit
:
- Ukázkový dotaz k zobrazení chyby odhadu mohutnosti v PostgreSQL
- SQL INNER JOIN přes více tabulek se rovná syntaxi WHERE
Výchozí nastavení pro join_collapse_limit
je 8 , což znamená, že se Postgres nebude snažit změnit pořadí tabulek ve vašem FROM
klauzule a pořadí tabulek je relevantní .
Jedním ze způsobů, jak to obejít, by bylo rozdělit část kritickou z hlediska výkonu na CTE
jako @joop přidal komentář
. Nenastavujte join_collapse_limit
mnohem vyšší nebo časy pro plánování dotazů zahrnující mnoho spojených tabulek se zhorší.
O vašem zřetězeném datu s názvem data
:
cast(cast(e.ano jako varchar(4))||'-'||right('0'||cast(e.mes as varchar(2)),2)||' -'|| right('0'||cast(e.dia as varchar(2)),2) as varchar(10)) as data
Za předpokladu sestavujete ze tří číselných sloupců pro rok, měsíc a den, které jsou definovány NOT NULL
, použijte místo toho toto:
e.ano::text || to_char(e.mes2, 'FM"-"00')
|| to_char(e.dia, 'FM"-"00') AS data
O FM
modifikátor vzoru šablony:
Ale ve skutečnosti byste měli datum uložit jako datový typ date
pro začátek.
Také zjednodušené:
format('%s (%s)', p.abrev, p.prenome) AS determinador
Dotaz to nezrychlí, ale je mnohem čistší. Viz format()
.
Nejdříve všechny obvyklé rady pro optimalizaci výkonu platí:
Pokud se vám to všechno podaří, měli byste vidět mnohem rychlejší dotazy pro vše vzory.