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 это знаете, но планировщик предполагает линейное распределение.

Итак, как исправить?

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

  1. использовать OFFSET 0 фишка с подзапросом:

    SELECT *
      FROM
      (
         SELECT * FROM T WHERE user='abcd' OFFSET 0
      )
      ORDER BY timestamp 
      LIMIT 1;
    

    этот трюк защищает запрос от встраивания, поэтому внутренняя часть выполняется на он свой.

  2. использовать CTE (имени подзапрос):

    WITH s AS (
            SELECT * FROM T WHERE user='abcd'
    )
    SELECT *
      FROM s
     ORDER BY timestamp 
     LIMIT 1;
    

    в документации:

    полезным свойством запросов является то, что они являются вычисляется только один раз за выполнение родительского запроса, даже если они упоминаются более одного раза родительским запросом или братом с запросами.

  3. использовать 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. подробно о оценок строку в документах, конечно.