Запрос Postgres не выбирает индекс для столбца с условием ИЛИ - PullRequest
0 голосов
/ 03 мая 2018

У меня есть запрос, когда Postgres выполняет Hash-соединение со сканированием последовательности вместо Index-соединения с вложенным циклом, когда я использую условие ИЛИ. Это приводит к тому, что запрос занимает 2 секунды вместо завершения <100 мс. Я запустил VACUUM ANALYZE и перестроил индекс для таблицы PATIENTCHARTNOTE (которая составляет около 5 ГБ), но он все еще использует хеш-соединение. Есть ли у вас какие-либо предложения о том, как я могу улучшить это? </p>

explain analyze
SELECT Count (_pcn.id) AS total_open_note
FROM   patientchartnote _pcn
   INNER JOIN appointment _appt
           ON _appt.id = _pcn.appointment_id
   INNER JOIN patient _pt
           ON _pt.id = _appt.patient_id
   LEFT OUTER JOIN person _ps
                ON _ps.id = _pt.appuser_id
   WHERE  _pcn.active = true
   AND _pt.active = true
   AND _appt.datecomplete IS NULL
   AND _pcn.title IS NOT NULL
   AND _pcn.title <> ''
   AND ( _pt.assigned_to_user_id = '136964'
         OR  _pcn.createdby_id = '136964'
   );


 Aggregate  (cost=237655.59..237655.60 rows=1 width=8) (actual       time=1602.069..1602.069 rows=1 loops=1)
 ->  Hash Join  (cost=83095.43..237645.30 rows=4117 width=4) (actual time=944.850..1602.014 rows=241 loops=1)
 Hash Cond: (_appt.patient_id = _pt.id)
 Join Filter: ((_pt.assigned_to_user_id = 136964) OR (_pcn.createdby_id = 136964))
 Rows Removed by Join Filter: 94036
 ->  Hash Join  (cost=46650.68..182243.64 rows=556034 width=12) (actual time=415.862..1163.812 rows=94457 loops=1)
 Hash Cond: (_pcn.appointment_id = _appt.id)
 ->  Seq Scan on patientchartnote _pcn  (cost=0.00..112794.20 rows=1073978 width=12) (actual time=0.016..423.262 rows=1
073618 loops=1)
Filter: (active AND (title IS NOT NULL) AND ((title)::text <> ''::text))
Rows Removed by Filter: 22488
->  Hash  (cost=35223.61..35223.61 rows=696486 width=8) (actual time=414.749..414.749 rows=692839 loops=1)
Buckets: 131072  Batches: 16  Memory Usage: 2732kB
->  Seq Scan on appointment _appt  (cost=0.00..35223.61 rows=696486 width=8)        (actual time=0.010..271.208 rows=69
2839 loops=1)
Filter: (datecomplete IS NULL)
Rows Removed by Filter: 652426
->  Hash  (cost=24698.57..24698.57 rows=675694 width=12) (actual time=351.566..351.566 rows=674929 loops=1)
Buckets: 131072  Batches: 16  Memory Usage: 2737kB
->  Seq Scan on patient _pt  (cost=0.00..24698.57 rows=675694 width=12) (actual time=0.013..197.268 rows=674929 loops=
1)
Filter: active
Rows Removed by Filter: 17426
Planning time: 1.533 ms
Execution time: 1602.715 ms

Когда я заменяю "ИЛИ _pcn.createdby_id = '136964'" на "И _pcn.createdby_id = '136964'", Postgres выполняет сканирование индекса

 Aggregate  (cost=29167.56..29167.57 rows=1 width=8) (actual time=937.743..937.743 rows=1 loops=1)
 ->  Nested Loop  (cost=1.28..29167.55 rows=7 width=4) (actual time=19.136..937.669 rows=37 loops=1)
 ->  Nested Loop  (cost=0.85..27393.03 rows=1654 width=4) (actual time=2.154..910.250 rows=1649 loops=1)
 ->  Index Scan using patient_activeassigned_idx on patient _pt  (cost=0.42..3075.00 rows=1644 width=8) (actual time=1.
599..11.820 rows=1627 loops=1)
 Index Cond: ((active = true) AND (assigned_to_user_id = 136964))
 Filter: active
 ->  Index Scan using appointment_datepatient_idx on appointment _appt  (cost=0.43..14.75 rows=4 width=8) (actual time=
 0.543..0.550 rows=1 loops=1627)
 Index Cond: ((patient_id = _pt.id) AND (datecomplete IS NULL))
 ->  Index Scan using patientchartnote_activeappointment_idx on patientchartnote _pcn  (cost=0.43..1.06 rows=1 width=8) (actual time=0.014..0.014 rows=0 loops=1649)
 Index Cond: ((active = true) AND (createdby_id = 136964) AND (appointment_id = _appt.id) AND (title IS NOT NULL))
 Filter: (active AND ((title)::text <> ''::text))
 Planning time: 1.489 ms
 Execution time: 937.910 ms
 (13 rows)

1 Ответ

0 голосов
/ 03 мая 2018

Использование OR в запросах SQL обычно приводит к снижению производительности.

Это потому, что & ndash; отличается от AND & ndash; это не ограничивает, но увеличивает количество строк в результате запроса. С AND вы можете использовать сканирование индекса для одной части условия и дополнительно ограничить результирующий набор фильтром для второго условия. Это невозможно с OR.

Таким образом, PostgreSQL делает единственное, что осталось: он вычисляет все соединение, а затем отфильтровывает все строки, которые не соответствуют условию. Конечно, это очень неэффективно, когда вы объединяете три таблицы (я не учел внешнее соединение).

Предполагая, что все столбцы с именем id являются первичными ключами, вы можете переписать запрос следующим образом:

SELECT count(*) FROM
    (SELECT _pcn.id
     FROM   patientchartnote _pcn
        INNER JOIN appointment _appt
                ON _appt.id = _pcn.appointment_id
        INNER JOIN patient _pt
                ON _pt.id = _appt.patient_id
        LEFT OUTER JOIN person _ps
                     ON _ps.id = _pt.appuser_id
        WHERE  _pcn.active = true
        AND _pt.active = true
        AND _appt.datecomplete IS NULL
        AND _pcn.title IS NOT NULL
        AND _pcn.title <> ''
        AND _pt.assigned_to_user_id = '136964'
     UNION
     SELECT _pcn.id
     FROM   patientchartnote _pcn
        INNER JOIN appointment _appt
                ON _appt.id = _pcn.appointment_id
        INNER JOIN patient _pt
                ON _pt.id = _appt.patient_id
        LEFT OUTER JOIN person _ps
                     ON _ps.id = _pt.appuser_id
        WHERE  _pcn.active = true
        AND _pt.active = true
        AND _appt.datecomplete IS NULL
        AND _pcn.title IS NOT NULL
        AND _pcn.title <> ''
        AND _pcn.createdby_id = '136964'
    ) q;

Несмотря на то, что запрос выполняется дважды, индексы можно использовать для ранней фильтрации всех строк, кроме нескольких, поэтому этот запрос должен работать лучше.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...