PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Postgres не използва индекс, когато сканирането на индекса е много по-добър вариант

Сканиране на индекс (само) --> Сканиране на индекс на растерни изображения --> Последователно сканиране

За няколко реда си струва да стартирате сканиране на индекс. Ако достатъчно страници с данни са видими за всички (=достатъчно вакуумирани и не твърде много едновременно натоварване на запис) и индексът може да предостави всички необходими стойности на колоните, тогава се използва по-бързо сканиране само на индекс. С повече редове, които се очаква да бъдат върнати (по-висок процент от таблицата и в зависимост от разпределението на данните, честотите на стойността и ширината на реда) става по-вероятно да се намерят няколко реда на една страница с данни. След това си струва да преминете към сканиране с растерни изображения. (Или да комбинирате множество отделни индекси.) След като все пак трябва да се посетят голям процент от страниците с данни, е по-евтино да изпълните последователно сканиране, да филтрирате излишните редове и да пропуснете режийните разходи за индекси като цяло.

Използването на индекс става (много) по-евтино и по-вероятно, когато достъпът до страници с данни в произволен ред не е (много) по-скъп от достъпа до тях в последователен ред. Такъв е случаят, когато използвате 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);


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Как работи EXCEPT в PostgreSQL

  2. Стандартизиране на PostgreSQL сигурността в многооблачни среди

  3. Динамично наблюдение на екземпляри на PostgreSQL с помощта на pg_top

  4. Общо решение Ruby за SQLite3 LIKE или PostgreSQL ILIKE?

  5. Как да получа min/max от две цели числа в Postgres/SQL?