Оптимизировать запрос с несколькими условиями "между" - PullRequest
1 голос
/ 23 марта 2020

У меня есть таблица playground со столбцом val, столбец val проиндексирован.

У меня есть список диапазонов [(min1, max1), (min2, max2), ... , (minN, maxN)], и я хочу выбрать все строки с val, которые вписывается в любой из этих диапазонов.

Например, мои диапазоны выглядят так: [(1,5), (20,25), (200,400)] Вот простой запрос, который извлекает соответствующие строки:

select p.*
from playground p
where (val between 1 AND 5) or (val between 20 and 25) or
    (val between 200 and 400);

Проблема в том, что этот список range is dynamici c, мое приложение генерирует его и отправляет вместе с запросом на postgres.

Я пытался переписать запрос, чтобы принять динамический c список диапазонов:

select p.*
from playground p,
    unnest(ARRAY [(1, 5),(20, 25),(200, 400)]) as r(min_val INT, max_val INT)
where p.val between r.min_val and r.max_val;

Он извлекает те же строки, но я не знаю, эффективный запрос или нет?

Вот как выглядит объяснение для первого запроса:

Bitmap Heap Scan on playground p  (cost=12.43..16.45 rows=1 width=36) (actual time=0.017..0.018 rows=4 loops=1)
  Recheck Cond: (((val >= 1) AND (val <= 5)) OR ((val >= 20) AND (val <= 25)) OR ((val >= 200) AND (val <= 400)))
  Heap Blocks: exact=1
  ->  BitmapOr  (cost=12.43..12.43 rows=1 width=0) (actual time=0.012..0.012 rows=0 loops=1)
        ->  Bitmap Index Scan on playground_val_index  (cost=0.00..4.14 rows=1 width=0) (actual time=0.010..0.010 rows=3 loops=1)
              Index Cond: ((val >= 1) AND (val <= 5))
        ->  Bitmap Index Scan on playground_val_index  (cost=0.00..4.14 rows=1 width=0) (actual time=0.001..0.001 rows=0 loops=1)
              Index Cond: ((val >= 20) AND (val <= 25))
        ->  Bitmap Index Scan on playground_val_index  (cost=0.00..4.14 rows=1 width=0) (actual time=0.001..0.001 rows=1 loops=1)
              Index Cond: ((val >= 200) AND (val <= 400))
Planning Time: 0.071 ms
Execution Time: 0.057 ms

А вот объяснение для второго:

Nested Loop  (cost=0.14..12.52 rows=2 width=36) (actual time=0.033..0.065 rows=4 loops=1)
  ->  Function Scan on unnest r  (cost=0.00..0.03 rows=3 width=8) (actual time=0.011..0.012 rows=3 loops=1)
  ->  Index Scan using playground_val_index on playground p  (cost=0.13..4.15 rows=1 width=36) (actual time=0.008..0.015 rows=1 loops=3)
        Index Cond: ((val >= r.min_val) AND (val <= r.max_val))
Planning Time: 0.148 ms
Execution Time: 0.714 ms

ПРИМЕЧАНИЕ. В обоих случаях Я сделал set enable_seqscan = false;, чтобы индекс работал.

Я беспокоюсь о стадии "Nested L oop". Это нормально? Или есть более эффективные способы передачи динамического списка диапазонов c в запрос? Моя postgres версия 12.1

1 Ответ

1 голос
/ 25 марта 2020

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

Выстрел в темный: с неперекрывающимися диапазонами, UNION ALL запрос может обеспечить наилучшую производительность:

SELECT * FROM playground WHERE val BETWEEN   1 AND   5
UNION ALL
SELECT * FROM playground WHERE val BETWEEN  20 AND  25
UNION ALL
SELECT * FROM playground WHERE val BETWEEN 200 AND 400;

Мы знаем , что диапазоны не перекрываются, но Postgres нет, поэтому он должен выполнять дополнительную работу в ваших попытках. Этот запрос должен избегать как BitmapOr первого, так и Nested Loop второго плана. Просто выберите каждый диапазон и добавьте его к выводу. Результатом должен быть план, подобный следующему:

Append  (cost=0.13..24.50 rows=3 width=40)
  ->  Index Scan using playground_val_idx on playground  (cost=0.13..8.15 rows=1 width=40)
        Index Cond: ((val >= 1) AND (val <= 5))
  ->  Index Scan using playground_val_idx on playground playground_1  (cost=0.13..8.15 rows=1 width=40)
        Index Cond: ((val >= 20) AND (val <= 25))
  ->  Index Scan using playground_val_idx on playground playground_2  (cost=0.13..8.15 rows=1 width=40)
        Index Cond: ((val >= 200) AND (val <= 400))

Plus, каждый суб-SELECT будет основан на фактической статистике для данного диапазона, а не на общих c оценках, даже для более длинного списка диапазонов. Смотрите (рекомендуется!):

Вы можете сгенерировать запрос в своем клиенте или написать серверная функция для генерации и выполнения динамических c SQL (применимо, так как тип результата известен).

Вы можете даже протестировать серверную функцию, используя LOOP (который часто менее эффективно, но это может быть исключением):

CREATE OR REPLACE FUNCTION foo(_ranges int[])
  RETURNS SETOF playground LANGUAGE plpgsql PARALLEL SAFE STABLE AS
$func$
DECLARE
   _range   int[];
BEGIN
   FOREACH _range SLICE 1 IN ARRAY _ranges
   LOOP
      RETURN QUERY
      SELECT * FROM playground WHERE val BETWEEN _range[1] AND _range[2];
   END LOOP;
END
$func$;

Служебные расходы могут не оплачивать несколько диапазонов в вызове. Но очень удобно вызывать, если ничего больше:

SELECT * FROM foo('{{1,5},{20,25},{200,400}}');

Связанный:

дБ <> скрипка здесь

Физический порядок строк может помочь много, Если строки хранятся в последовательности, (намного) нужно обрабатывать меньше страниц данных. Зависит от нераскрытых деталей. В этом могут помочь встроенные CLUSTER или расширения pg_repack или pg_squeeze. Связанные:

И рекомендуется использовать последний доступный вспомогательный выпуск для какая бы основная версия не использовалась. На момент написания статьи было бы 12.2 (выпущено 2020-02-13).

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