В вашей таблице 54 миллиона строк и ...
существует около 4 миллионов таких значений
7,4% всех строк - это высокий процент, индекс в большинстве случаев может помочь только с помощью предварительно отсортированных данных, в идеале при сканировании только по индексу . Существуют более сложные методы для меньших наборов результатов (см. Ниже), и есть намного более быстрых способов подкачки, которые возвращают намного меньше строк за раз (см. Ниже), но для общего случая обычная DISTINCT
может быть одним из самых быстрых:
SELECT DISTINCT a, b -- *no* parentheses
FROM tbl;
-- ORDER BY a, b -- ORDER BY wasn't not mentioned as requirement ...
Не путайте это с DISTINCT ON
, для которого потребуются скобки. См:
Индекс B-дерева ab_index
, установленный на (a, b)
, уже является лучшим индексом для этого. Тем не менее, он должен быть отсканирован полностью. Задача состоит в том, чтобы иметь достаточно work_mem
для обработки всего в RAM . При стандартных настройках он занимает не менее 1831 МБ на диске, как правило, больше с некоторым раздуванием. Если вы можете себе это позволить, запустите запрос с настройкой work_mem
2 ГБ (или более) в своем сеансе. См:
SET work_mem = '2 GB';
SELECT DISTINCT a, b ...
RESET work_mem;
Таблица только для чтения помогает. В противном случае вам понадобятся достаточно агрессивные настройки VACUUM
, чтобы разрешить сканирование только по индексу . И еще немного ОЗУ поможет (с соответствующими настройками) сохранить индекс в кэше.
Также обновите до последней версии Postgres (11.3 на момент написания). Для больших данных было много улучшений.
* * Пейджинг тысяча сорок-девять
Если вы хотите добавить paging , как указано в вашем примере запроса, срочно рассмотрите Сравнение значений ROW . См:
SELECT DISTINCT a, b
FROM tbl
<b>WHERE (a, b) > ($previous_a, $previous_b) -- !!!</b>
ORDER BY a, b
LIMIT 1000;
рекурсивный CTE
Это также может быть или не быть быстрее для общего большого запроса. Для небольшого подмножества оно становится намного более привлекательным:
WITH RECURSIVE cte AS (
( -- parentheses required du to LIMIT 1
SELECT a, b
FROM tbl
<b>WHERE (a, b) > ($previous_a, $previous_b)</b> -- !!!
ORDER BY a, b
LIMIT 1
)
UNION ALL
SELECT x.a, x.b
FROM cte c
CROSS JOIN LATERAL (
SELECT t.a, t.b
FROM tbl t
WHERE (t.a, t.b) > (c.a, c.b) -- lateral reference
ORDER BY t.a, t.b
LIMIT 1
) x
)
TABLE cte
LIMIT 1000;
Это может отлично использовать ваш индекс и должно быть так же быстро, как он получает .
Дальнейшее чтение:
Для многократного использования и без или с небольшой загрузкой записи в таблицу рассмотрите MATERIALIZED VIEW
, основанный на одном из вышеупомянутых запросов - для гораздо более высокой производительности чтения.