Защо?
Причината е това:
Бърза заявка:
-> 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)) ~~* (...)
Бавна заявка:
-> 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 (...)
Разширяването на шаблона за търсене с друг знак кара Postgres да приема още по-малко попадения. (Обикновено това е разумна оценка.) Postgres очевидно няма достатъчно прецизна статистика (всъщност никаква, продължавайте да четете), за да очаквате същия брой посещения, който наистина получавате.
Това води до превключване към различен план за заявка, който е дори по-малко оптимален за действителните брой посещения rows=1129
.
Решение
Приемаме текущия Postgres 9.5, тъй като не е деклариран.
Един от начините да подобрите ситуацията е да създадете индекс на израза върху израза в предиката. Това кара Postgres да събира статистика за действителния израз, което може да помогне на заявката дори ако самият индекс не се използва за заявката . Без индекса няма няма статистика за израза изобщо. И ако се направи правилно, индексът може да се използва за заявката, това е дори много по-добре. Но има множество проблеми с текущия ви израз:
uaccent(TEXT(coalesce(p.abrev,'')||' ('||coalesce(p.prenome,'')||')')) ilike uaccent('%vicen%' )
Разгледайте тази актуализирана заявка въз основа на някои предположения относно вашите неразкрити дефиниции на таблици:
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' || '%');
Основни точки
Защо f_unaccent()
? Тъй като unaccent()
не може да се индексира. Прочетете това:
Използвах функцията, описана там, за да позволя следния (препоръчителен!) многоколонен функционален GIN индекс триграма :
CREATE INDEX pess_unaccent_nome_trgm_idx ON pess
USING gin (f_unaccent(pess) gin_trgm_ops, f_unaccent(prenome) gin_trgm_ops);
Ако не сте запознати с триграмните индекси, първо прочетете това:
И евентуално:
Не забравяйте да стартирате най-новата версия на Postgres (в момента 9.5). Има съществени подобрения на GIN индексите. И ще се интересувате от подобрения в pg_trgm 1.2, планиран да бъде пуснат с предстоящия Postgres 9.6:
Подготвени извлечения са често срещан начин за изпълнение на заявки с параметри (особено с текст от потребителско въвеждане). Postgres трябва да намери план, който работи най-добре за даден параметър. Добавете заместващи символи като константи към думата за търсене по този начин:
f_unaccent(p.abrev) ILIKE f_unaccent('%' || 'vicenti' || '%')
('vicenti'
ще бъде заменено с параметър.) Така че Postgres знае, че имаме работа с модел, който не е закотвен нито отляво, нито отдясно - което би позволило различни стратегии. Свързан отговор с повече подробности:
Или може би препланирайте заявката за всеки термин за търсене (възможно е да използвате динамичен SQL във функция). Но се уверете, че времето за планиране не пречи на възможното повишаване на производителността.
КЪДЕ
условие за колони в pess
противоречи на . Postgres е принуден да преобразува това в LEFT JOIN
INNER JOIN
. Което е по-лошо, присъединяването идва късно в дървото на присъединяването. И тъй като Postgres не може да пренареди вашите съединения (вижте по-долу), това може да стане много скъпо. Преместете масата напървата позиция в ОТ
клауза за ранно премахване на редове. След LEFT JOIN
s не елиминират никакви редове по дефиниция. Но с толкова много таблици е важно да се преместват съединения, които могат да умножават редове до края.
Вие се присъединявате към 13 маси, 12 от тях с LEFT JOIN
което оставя 12!
възможни комбинации - или 11! * 2!
ако вземем едното LEFT JOIN
предвид това наистина е INNER JOIN
. Това е също много за Postgres, за да оцени всички възможни пермутации за най-добрия план за заявка. Прочетете за join_collapse_limit
:
- Примерна заявка за показване на грешка при оценка на кардиналността в PostgreSQL
- SQL INNER JOIN над множество таблици, равен на WHERE синтаксис
Настройката по подразбиране за join_collapse_limit
е8 , което означава, че Postgres няма да се опита да пренареди таблици във вашия FROM
клауза и редът на таблиците е уместен .
Един от начините да се заобиколи това би бил да се раздели критичната за производителността част на CTE
като @joop коментира
. Не задавайте join_collapse_limit
много по-високи или времената за планиране на заявки, включващи много обединени таблици, ще се влошат.
Относно вашата свързана дата с име данни
:
cast(cast(e.ano as varchar(4))||'-'||right('0'||cast(e.mes as varchar(2)),2)||' -'|| right('0'||cast(e.dia като varchar(2)),2) като varchar(10)) като данни
Приемаме изграждате от три цифрови колони за година, месец и ден, които са дефинирани NOT NULL
, използвайте това вместо това:
e.ano::text || to_char(e.mes2, 'FM"-"00')
|| to_char(e.dia, 'FM"-"00') AS data
Относно FM
модификатор на модел на шаблон:
Но наистина трябва да съхранявате датата като тип данни date
като начало.
Също опростено:
format('%s (%s)', p.abrev, p.prenome) AS determinador
Няма да направи заявката по-бърза, но е много по-чиста. Вижте format()код>
.
Първо, накрая, всички обичайни съвети за оптимизиране на производителността се прилага:
Ако направите всичко това правилно, трябва да видите много по-бързи заявки за всички шаблони.