PostgreSQL использует другой индекс для одного и того же запроса

У меня есть SQL-запрос, который использует внутреннее соединение двух таблиц и фильтрует данные на основе нескольких параметров. Следуя плану запроса, для разных значений параметров запроса (например, для разных диапазонов дат) Postgres использует другой индекс.

Мне известно, что Postgres определяет, нужно ли использовать индекс или нет, в зависимости от числа или строк в результирующем наборе. Но почему Postgres предпочитает использовать разные индексы для одного и того же запроса. Время запроса различается в 10 раз между двумя случаями. Как оптимизировать запрос? Поскольку Postgres не позволяет пользователю определять индекс, который будет использоваться в запросе.

Редактировать:

explain (analyze, buffers, verbose) SELECT COUNT(*) FROM "bookings" INNER JOIN "hotels" ON "hotels"."id" = "bookings"."hotel_id" WHERE "bookings"."hotel_id" = 37016 AND (bookings.status in (0,1,2,3,4,5,6,7,9,10,11,12)) AND (bookings.source in (0,1,2,3,4,5,6,7,8,9,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70) or bookings.status in (0,1,2,3,4,5,6,7,8,9,10,11,13)) AND (
bookings.source in (4,66,65)
OR
date(timezone('+05:30',bookings.created_at))>checkin
OR
(
( date(timezone('+05:30',bookings.created_at))=checkin
and
extract (epoch from COALESCE(cancellation_time,NOW())-bookings.created_at)>600
)
OR
( date(timezone('+05:30',bookings.created_at))<checkin
and
extract (epoch from COALESCE(cancellation_time,NOW())-bookings.created_at)>600
and
(
extract (epoch from ((bookings.checkin||' '||hotels.checkin_time)::timestamp -COALESCE(cancellation_time,bookings.checkin))) < extract(epoch from '16 hours'::interval)
OR
(DATE(bookings.checkout)-DATE(bookings.checkin))*(COALESCE(bookings.oyo_rooms,0)+COALESCE(bookings.owner_rooms,0)) > 3
)
)
)
) AND (bookings.checkin >= '2018-11-21') AND (bookings.checkin <= '2019-05-19') AND "bookings"."hotel_id" = '37016' AND "bookings"."status" IN (0, 1, 2, 3, 12);

План запроса: https://explain.depesz.com/s/SPeb

explain (analyze, buffers, verbose) SELECT COUNT(*) FROM "bookings" INNER JOIN "hotels" ON "hotels"."id" = 37016 WHERE "bookings"."hotel_id" = 37016 AND (bookings.status in (0,1,2,3,4,5,6,7,9,10,11,12)) AND (bookings.source in (0,1,2,3,4,5,6,7,8,9,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70) or bookings.status in (0,1,2,3,4,5,6,7,8,9,10,11,13)) AND (
bookings.source in (4,66,65)
OR
date(timezone('+05:30',bookings.created_at))>checkin
OR
(
( date(timezone('+05:30',bookings.created_at))=checkin
and
extract (epoch from COALESCE(cancellation_time,now())-bookings.created_at)>600
)
OR
( date(timezone('+05:30',bookings.created_at))<checkin
and
extract (epoch from COALESCE(cancellation_time,now())-bookings.created_at)>600
and
(extract (epoch from ((bookings.checkin||' '||hotels.checkin_time)::timestamp -COALESCE(cancellation_time,bookings.checkin))) < extract(epoch from '16 hours'::interval)
OR
(DATE(bookings.checkout)-DATE(bookings.checkin))*(COALESCE(bookings.oyo_rooms,0)+COALESCE(bookings.owner_rooms,0)) > 3
)
)
)
) AND (bookings.checkin >= '2018-11-22') AND (bookings.checkin <= '2019-05-19') AND "bookings"."hotel_id" = '37016' AND "bookings"."status" IN (0,1,2,3,4,12);

План запроса: https://explain.depesz.com/s/DWD


person Raman Preet Singh    schedule 22.11.2018    source источник
comment
@a_horse_with_no_name, отредактировал сообщение.   -  person Raman Preet Singh    schedule 22.11.2018
comment
Не связано, но: два условия в конце AND "bookings"."hotel_id" = '37016' AND "bookings"."status" IN (0, 1, 2, 3, 12) бесполезны, потому что в начале есть WHERE "bookings"."hotel_id" = 37016, а также больший диапазон состояний: AND (bookings.status IN (0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12))   -  person a_horse_with_no_name    schedule 22.11.2018
comment
Кажется, что с большим диапазоном дат оптимизатор ожидает больше строк и поэтому использует другой индекс. Запуск analyze bookings (или vacuum analyze bookings) ничего не меняет?   -  person a_horse_with_no_name    schedule 22.11.2018
comment
Различные списки IN также могут иметь значение,   -  person Laurenz Albe    schedule 22.11.2018


Ответы (1)


Наконец нашел решение этой проблемы. Я делаю запрос на основе более чем 10 возможных значений столбца (в данном случае status). Если я разобью этот запрос на несколько подзапросов, каждый из которых запрашивает только одно значение состояния, и агрегирую результат, используя объединение всех, тогда выполненный план запроса использует оптимизированный индекс для каждого подзапроса.

Результаты: Благодаря этому изменению время запроса уменьшилось в 10 раз.

Возможное объяснение такого поведения: планировщик запросов выбирает меньшее количество строк для каждого подзапроса и в этом случае использует оптимизированный индекс. Я не уверен, что это правильное объяснение.

person Raman Preet Singh    schedule 18.12.2018