Сканиране на индекс (само) --> Сканиране на индекс на растерни изображения --> Последователно сканиране
За няколко реда си струва да стартирате сканиране на индекс. Ако достатъчно страници с данни са видими за всички (=достатъчно вакуумирани и не твърде много едновременно натоварване на запис) и индексът може да предостави всички необходими стойности на колоните, тогава се използва по-бързо сканиране само на индекс. С повече редове, които се очаква да бъдат върнати (по-висок процент от таблицата и в зависимост от разпределението на данните, честотите на стойността и ширината на реда) става по-вероятно да се намерят няколко реда на една страница с данни. След това си струва да преминете към сканиране с растерни изображения. (Или да комбинирате множество отделни индекси.) След като все пак трябва да се посетят голям процент от страниците с данни, е по-евтино да изпълните последователно сканиране, да филтрирате излишните редове и да пропуснете режийните разходи за индекси като цяло.
Използването на индекс става (много) по-евтино и по-вероятно, когато достъпът до страници с данни в произволен ред не е (много) по-скъп от достъпа до тях в последователен ред. Такъв е случаят, когато използвате SSD вместо въртящи се дискове, или дори повече, толкова повече се кешира в RAM - и съответните параметри на конфигурация random_page_cost
и effective_cache_size
са зададени съответно.
Във вашия случай Postgres преминава към последователно сканиране, очаквайки да намери rows=263962
, това вече е 3 % от цялата таблица. (Докато само rows=47935
действително са намерени, вижте по-долу.)
Повече в този свързан отговор:
- Ефективна PostgreSQL заявка за времеви печат, използвайки индексно или растерно сканиране на индекс?
Пазете се от принудителни планове за заявки
Не можете да наложите определен метод за планиране директно в Postgres, но можете да направите друг методите изглеждат изключително скъпи за целите на отстраняване на грешки. Вижте Конфигуриране на метода на планировщика в ръководството.
SET enable_seqscan = off
(както е предложено в друг отговор) прави това за последователни сканирания. Но това е предназначено само за отстраняване на грешки във вашата сесия. Праветене използвайте това като обща настройка в производството, освен ако не знаете точно какво правите. Може да наложи нелепи планове за заявки. Ръководството:
Тези конфигурационни параметри осигуряват груб метод за влияние върху плановете за заявка, избрани от оптимизатора на заявки. Ако планът по подразбиране, избран от оптимизатора за конкретна заявка, не е оптимален,временен решението е да използвате един от тези конфигурационни параметри, за да принудите оптимизатора да избере различен план. По-добрите начини за подобряване на качеството на плановете, избрани от оптимизатора, включват коригиране на разходните константи на планера (вижте раздел 19.7.2), изпълняване на
ANALYZE
ръчно, увеличавайки стойността наdefault_statistics_target
конфигурационен параметър и увеличаване на количеството статистически данни, събрани за конкретни колони, като се използваALTER TABLE SET STATISTICS
.
Това вече е повечето от съветите, от които се нуждаете.
- Пазете PostgreSQL понякога да избира лош план за заявка
В този конкретен случай Postgres очаква 5-6 пъти повече посещения на email_activities.email_recipient_id
отколкото са действително открити:
приблизително
rows=227007
спрямоactual ... rows=40789
приблизителноrows=263962
спрямоactual ... rows=47935
Ако изпълнявате тази заявка често, ще ви се струва, че имате ANALYZE
погледнете по-голяма извадка за по-точни статистически данни за конкретната колона. Вашата таблица е голяма (~ 10 милиона реда), така че направете това:
ALTER TABLE email_activities ALTER COLUMN email_recipient_id
SET STATISTICS 3000; -- max 10000, default 100
След това ANALYZE email_activities;
Мярка в краен случай
В много рядко случаи, в които можете да прибягвате до принудително въвеждане на индекс с SET LOCAL enable_seqscan = off
в отделна транзакция или във функция със собствена среда. Като:
CREATE OR REPLACE FUNCTION f_count_dist_recipients(_email_campaign_id int, _limit int)
RETURNS bigint AS
$func$
SELECT COUNT(DISTINCT a.email_recipient_id)
FROM email_activities a
WHERE a.email_recipient_id IN (
SELECT id
FROM email_recipients
WHERE email_campaign_id = $1
LIMIT $2) -- or consider query below
$func$ LANGUAGE sql VOLATILE COST 100000 SET enable_seqscan = off;
Настройката се отнася само за локалния обхват на функцията.
Предупреждение: Това е само доказателство за концепцията. Дори тази много по-малко радикална ръчна намеса може да ви ухапе в дългосрочен план. Кардиналите, стойностни честоти, вашата схема, глобални настройки на Postgres, всичко се променя с времето. Ще надстроите до нова версия на Postgres. Планът за заявка, който налагате сега, може да стане много лоша идея по-късно.
И обикновено това е просто решение за проблем с вашата настройка. По-добре го намерете и поправете.
Алтернативна заявка
Във въпроса липсва основна информация, но тази еквивалентна заявка вероятно е по-бърза и е по-вероятно да използва индекс за (email_recipient_id
) - все повече за по-голям LIMIT
.
SELECT COUNT(*) AS ct
FROM (
SELECT id
FROM email_recipients
WHERE email_campaign_id = 1607
LIMIT 43000
) r
WHERE EXISTS (
SELECT FROM email_activities
WHERE email_recipient_id = r.id);