Postgresql ORDER BY-выбор правильного индекса
есть таблица T(user, timestamp,...)
со 100 мл + записей (PostgreSQL 9.1).
запрос вида
SELECT *
FROM T
WHERE user='abcd'
ORDER BY timestamp
LIMIT 1
использует вместо индекса пользователя, когда есть ~100000 записей пользователей.
использование индекса метки времени всегда даст плохие результаты (20+ sec), поскольку он в конечном итоге сканирует все записи. Обход timestamp
индекс, изменив запрос на использование ORDER BY DATE(timestamp)
приведет к запросу, чтобы прибегнуть к индексу пользователя и дать результаты, которые меньше 100 ms.
- общая ОЗУ: 64 ГБ
- shared_buffers: 16 ГБ
- сортировки: 32 МБ
почему postgresql игнорирует и timestamp
индекс вместо этого (индекс метки времени должен будет видеть все записи)?
Существуют ли какие-либо параметры конфигурации postgresql, которые можно изменить, чтобы запрос использовал сам индекс имени пользователя?
1 ответов
хороший вопрос, я пришел вокруг этой проблемы некоторое время назад.
почему это происходит?
вы должны посмотреть в число user='abcd'
значение статистика такой:
SELECT attname, null_frac, ag_width, n_distinct,
most_common_vals, most_common_freqs, histogram_bounds
FROM pg_stats
WHERE table_name='T';
я предполагаю , что это значение встречается довольно часто, и вы найдете его в most_common_vals
выход.
Выбор одного и того же элемента из most_common_freqs
вы получите отношение для значения, умножьте его на общее количество строк (можно получить из pg_class
), чтобы получить количество строк оценивается иметь 'abcd'
значение.
планировщик предполагает, что все значения имеют линейное распределение. В реальности вещи конечно разные. Кроме того, в настоящее время нет коррелируется статистика (хотя в этом направлении ведется определенная работа).
Итак, давайте рассмотрим user='abcd'
значением, имеющего 0.001
соотношение (на вопрос) в соответствующем most_common_freqs
запись. Этот среднее значение будет происходить каждые 1000 строк (при условии линейного распределения). Оказывается, что если мы отсканируем таблицу в любом случае мы ударим по нашим user='abcd'
в некоторых 1000 строк. Звучит, что это должно быть быстро! Планировщик "думает" то же самое и выбирает индекс на .
но это не так. Если мы предположим, что ваш стол T
содержит журналы активности пользователя и user='abcd'
был в отпуске в течение последних 3 недель, то это означает, что нам придется прочитать довольно много строк из timestamp
индекс (3 недели стоит данных), прежде чем мы фактически попали в строку, которую мы хотим. Ну, вы как DBA это знаете, но планировщик предполагает линейное распределение.
Итак, как исправить?
вам придется обмануть планировщик, чтобы использовать то, что вам нужно, так как у вас есть больше знаний о ваших данных.
-
использовать
OFFSET 0
фишка с подзапросом:SELECT * FROM ( SELECT * FROM T WHERE user='abcd' OFFSET 0 ) ORDER BY timestamp LIMIT 1;
этот трюк защищает запрос от встраивания, поэтому внутренняя часть выполняется на он свой.
-
использовать
CTE
(имени подзапрос):WITH s AS ( SELECT * FROM T WHERE user='abcd' ) SELECT * FROM s ORDER BY timestamp LIMIT 1;
в документации:
полезным свойством запросов является то, что они являются вычисляется только один раз за выполнение родительского запроса, даже если они упоминаются более одного раза родительским запросом или братом с запросами.
-
использовать
count(*)
для aggrgated запросы:SELECT min(session_id), count(*) -- instead of simply `min(session_id)` FROM T WHERE user='abcd' ORDER BY timestamp LIMIT 1;
это не совсем применимо, но я хотел упомянуть.
и, пожалуйста, рассмотрите возможность обновления до 9.3.
P. S. подробно о оценок строку в документах, конечно.