Помощь в оптимизации запроса для базы данных Postgresql - PullRequest
1 голос
/ 30 октября 2009

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

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

То, что я уже сделал, чтобы ускорить это (потому что это занимало несколько минут), заключается в следующем: вместо того, чтобы запрашивать каждого пользователя по отдельности, скрипт теперь просто циклически просматривает каждого пользователя и вычисляет диапазон дат, который будет применим к ним все остальные аспекты запроса идентичны для каждого пользователя). Эти данные собираются в трехмерном массиве [startDate] [endDate] [userid], и один запрос создается для всех пользователей. Вот пример запроса, который получает вывод:


SELECT COUNT(uapl.id) AS numAnswered,
SUM(CASE WHEN (a.correct OR q.survey OR uapl.answersId IS NULL) THEN 1 ELSE 0 END) AS numCorrect
FROM usersAnswersProgramsLink uapl
JOIN questions q ON uapl.questionsId=q.id
LEFT JOIN answers a ON uapl.answersId=a.id
WHERE
programsId=123
AND
(
  (
    CAST(timestamp AS date) >= '2009-09-01'
    AND CAST(timestamp AS date) <= '2009-09-21'
    AND usercontextid in('123','234','345','465','567')
  )
  OR
  (
    CAST(timestamp AS date) >= '2009-09-10'
    AND CAST(timestamp AS date) <= '2009-09-21'
    AND usercontextid in('321','432','543')
  )
  OR
  (
    CAST(timestamp AS date) >= '2009-09-16'
    AND CAST(timestamp AS date) <= '2009-09-21'
    AND usercontextid in('987','876')
  )
) 

Это работает относительно хорошо при ускорении кода. В большинстве тестов, которые я выполняю, теперь это занимает от 20% до 10%. В моем худшем случае это всего лишь 50%, и я бы хотел улучшить это.

Этот наихудший случай случается, когда я сравниваю огромное количество идентификаторов пользователей (десять тысяч). Проблема сейчас в том, что больше нет необходимости оптимизировать алгоритм, из которого я извлек эти запросы. Это теперь проходит в миллисекундах. Этот запрос занимает много времени.

Так что это моя загадка. Я хотел бы ускорить это еще больше. Любые предложения будут приветствоваться. Пару информации, которая уместна здесь:

1) Между диапазонами дат и пользователями существует отношение 1 ко многим. ни один из этих идентификаторов пользователя не будет отображаться в нескольких диапазонах дат. 2) Конечным результатом, который мы ищем, являются именно эти подсчеты, но диапазоны дат необходимо рассчитывать для каждого пользователя, отсюда и массив идентификаторов для диапазона дат.

Одна вещь, которую я подумал ~ может ~ сделать это быстрее, - это создать временную таблицу со столбцом для диапазона дат и столбцом для идентификаторов пользователя. Затем перепишите этот запрос, используя JOIN для этой таблицы, а не помещая эти числа в сам запрос. Кто-нибудь знает, будет ли это работать?

Спасибо за любые предложения!

Ответы [ 3 ]

2 голосов
/ 31 октября 2009

, как уже упоминалось: укажите результат EXPLAIN ANALYZE <query>, а также структуры таблиц и созданные индексы, без этого вам будет трудно помочь

может помочь индекс на timestamp::date (индекс на временной метке не будет использоваться из-за приведения)

вы также можете опубликовать вывод explain analyze в http://explain.depesz.com/, который выделит проблемные места в плане выполнения

0 голосов
/ 30 октября 2009

Во-первых, я бы предложил добавить грубый фильтр, который будет использовать индексы usercontextid и timestamp:

SELECT  COUNT(uapl.id) AS numAnswered,
        SUM(CASE WHEN (a.correct OR q.survey OR uapl.answersId IS NULL) THEN 1 ELSE 0 END) AS numCorrect
FROM    questions q
JOIN    usersAnswersProgramsLink uapl
ON      uapl.questionsId = q.id
LEFT JOIN
        answers a
ON      a.id = uapl.answersId
WHERE   programsId=123
        AND timestamp >= '2009-09-01'
        AND timestamp < '2009-09-22'
        AND usercontextid IN (/* all possible values here */)
        AND 
(
  (
    CAST(timestamp AS date) >= '2009-09-01'
    AND CAST(timestamp AS date) <= '2009-09-21'
    AND usercontextid in('123','234','345','465','567')
  )
  OR
  (
    CAST(timestamp AS date) >= '2009-09-10'
    AND CAST(timestamp AS date) <= '2009-09-21'
    AND usercontextid in('321','432','543')
  )
  OR
  (
    CAST(timestamp AS date) >= '2009-09-16'
    AND CAST(timestamp AS date) <= '2009-09-21'
    AND usercontextid in('987','876')
  )
)

Вам также необходимо уточнить, к каким таблицам относятся все эти поля.

0 голосов
/ 30 октября 2009

Одна вещь, которую я подумал ~ может ~ сделать это было бы быстрее, чтобы ящик временная таблица с колонкой для диапазон дат и столбец для пользователя идентификаторы. Затем перепишите этот запрос, используя ПРИСОЕДИНЯЙТЕСЬ к этому столу вместо эти цифры в самом запросе. Кто-нибудь знает, будет ли это работать?

Это был бы подход, который я выбрал бы. Это также сделает запрос более понятным. Вы также можете добавить индексы во временную таблицу, хотя это следует делать после заполнения ее данными. Не думайте, что вам нужен индекс, хотя - тестируйте.

Да, возможно, вы захотите хранить временные метки, а не даты (это сохранит приведение) и, возможно, индекс в столбце «временные метки» в таблице ответов.

PS - обычно считается, что столбцы не следует называть так же, как встроенные типы. Даже если база данных не запутается, читатель сможет.

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