#sql #database #postgresql
#sql #База данных #postgresql
Вопрос:
У меня проблемы с производительностью этого запроса. Если я удалю порядок по разделам, все будет работать хорошо. Но я действительно этого хочу. Я пытался использовать много индексов, но не получил никаких результатов. Можете ли вы мне помочь, пожалуйста?
SELECT *
FROM "refuel_request" AS "refuel_request"
LEFT OUTER JOIN "user" AS "user" ON "refuel_request"."user_id" = "user"."user_id"
LEFT OUTER JOIN "bill_qr" AS "bill_qr" ON "refuel_request"."bill_qr_id" = "bill_qr"."bill_qr_id"
LEFT OUTER JOIN "car" AS "order.car" ON "refuel_request"."car_id" = "order.car"."car_id"
LEFT OUTER JOIN "refuel_request_status" AS "refuel_request_status" ON "refuel_request"."refuel_request_status_id" = "refuel_request_status"."refuel_request_status_id"
WHERE
refuel_request."refuel_request_status_id" IN ( '1', '2', '3')
ORDER BY "refuel_request".created_at desc
LIMIT 10
Существует объяснение этого запроса
Первичные ключи и / или внешние ключи
- pk_refuel_request_id
- refuel_request_bill_qr_id_fkey
- refuel_request_user_id_fkey
Комментарии:
1. Есть ли у вас какие-либо первичные ключи и / или настроенные внешние ключи? Какие индексы вы пробовали? (пожалуйста, добавьте эти данные в свой вопрос)
2. Спасибо за включение плана объяснения, но не могли бы вы заменить его результатом
EXPLAIN (ANALYZE, BUFFERS)
? Это дает нам больше информации для работы.3. Одна вещь, которую я замечаю, это то, что эти строки кажутся довольно большими. Я бы рекомендовал указать нужные вам столбцы в select вместо использования подстановочного знака.
4. План выполнения лучше использовать в виде форматированного текста (так же, как вы предоставили свой запрос), а не в виде снимка экрана.
5. @BlueStar добавил ОБЪЯСНЕНИЕ (АНАЛИЗ, БУФЕРЫ), но мне нужны все они для страницы (
Ответ №1:
Все внешние таблицы объединения связаны с 1:n refuel_request
. Это означает, что ваш запрос ищет последние десять созданных запросов на заправку со статусом от 1 до 3.
Вы выполняете внешнее соединение таблиц, потому что не каждая reful_request
связана с a user
, a bill_qr
, a car
и a status
. Или вы ошибочно подключаетесь к внешнему соединению. В любом случае, ни одно из объединений не изменяет количество извлекаемых строк; это все еще одна строка на запрос на дозаправку. Для объединения строк других таблиц СУБД просто нужны их индексы первичного ключа. Беспокоиться не о чем.
Единственное, о чем мы должны заботиться reful_request
, это как можно быстрее найти верхние строки для интересующих вас статусов.
Используйте частичный индекс, который содержит только данные для рассматриваемых статусов. Столбец, который вы индексируете, является created_at
столбцом, чтобы сразу получить 10 лучших.
CREATE INDEX idx ON refuel_request (created_at DESC)
WHERE refuel_request_status_id IN (1, 2, 3);
Здесь объясняются частичные индексы: https://www.postgresql.org/docs/current/indexes-partial.html
Комментарии:
1. Тай, я постараюсь!
Ответ №2:
У вас не может быть индекса, который поддерживает как WHERE
условие, так и ORDER BY
, потому что вы используете IN
, а не =
.
Самый быстрый вариант — разделить запрос на три части, чтобы каждая часть сравнивалась refuel_request.refuel_request_status_id
с =
. Объедините эти три запроса с UNION ALL
помощью . В каждом из запросов есть ORDER BY
и LIMIT 10
, и вы оборачиваете все это во внешний запрос, в котором есть другой ORDER BY
и LIMIT 10
.
Тогда вам нужны эти индексы:
CREATE INDEX ON refuel_request (refuel_request_status_id, created_at);
CREATE INDEX ON "user" (user_id);
CREATE INDEX ON bill_qr (bill_qr_id);
CREATE INDEX ON car (car_id);
CREATE INDEX ON refuel_request_status (refuel_request_status_id);
Ответ №3:
Вам нужны хотя бы индексы для соединений (вам действительно нужны левые соединения?)
LEFT OUTER JOIN "user" AS "user" ON "refuel_request"."user_id" = "user"."user_id"
Итак, refuel_request.user_id должен быть в индексе
LEFT OUTER JOIN "bill_qr" AS "bill_qr" ON "refuel_request"."bill_qr_id" =
LEFT OUTER JOIN "car" AS "order.car" ON "refuel_request"."car_id" =
bill_qr_id и car_id тоже
LEFT OUTER JOIN "refuel_request_status" AS "refuel_request_status" ON "refuel_request"."refuel_request_status_id" =
и refuel_request_status_id
WHERE
refuel_request."refuel_request_status_id" IN ( '1', '2', '3')
refuel_request_status_id должен быть первым ключом в индексе, так как он нам нужен в WHERE
ORDER BY "refuel_request".created_at desc
а затем created_at, поскольку он находится в ORDER
предложении. Это не улучшит производительность как таковую, но позволит выполнить ORDER BY, не требуя доступа к данным таблицы, по той же причине, по которой мы помещаем туда другие столбцы, отличные от WHERE. Конечно, частичный индекс еще лучше, мы сдвигаем WHERE в предложении partiality и используем created_at
для остальных (ОГРАНИЧЕНИЕ 10 теперь означает, что мы можем обойтись без дополнительных столбцов в индексе, поскольку извлечение трех строк 1: N стоит очень мало; в другой ситуации мы могли бы найти это полезнымчтобы сохранить эти дополнительные столбцы).
Итак, один индекс, который содержит, в таком порядке:
refuel_request_status_id, created_at, bill_qr_id, car_id too, user_id
^ WHERE ^ ORDER ^ used by the JOINS
Однако вам действительно нужен SELECT *
? Я считаю, что вы получили бы лучшую производительность, если бы включили только те поля, которые вы действительно собираетесь использовать.
Комментарии:
1. Я пытался включить поля, которые мне действительно нужны, но это не изменило производительность. Теперь я пытаюсь добавить индекс refuel_request_status_id, created_at, bill_qr_id, car_id, user_id и сообщить вам, как изменилась производительность
2. @LaurenzAlbe на самом деле да, я могу. Запрос будет выполнен по индексу; конечно, наличие частичного индекса является правильным решением, но даже в этом случае на первом этапе объединения требуется доступ только к одному индексу. Если только затраты на выбранные дополнительные столбцы не делают целесообразным выполнение последовательного сканирования, но это еще одна проблема.
3. @LaurenzAlbe подождите, это недоразумение. Я не собирался полагаться на индекс для ПОРЯДКА ПО — я хотел его только для своих данных. Таким же образом используется покрывающий индекс, если хотите. Преимущество заключается в том, что ORDER BY не требует доступа к таблице . Я все еще ожидаю, что серверу потребуется полное сканирование индекса , это данность (добавлено к ответу, чтобы было понятнее).
4. Спасибо за разъяснение.
Ответ №4:
Наиболее эффективным индексом для этого запроса будет refuel_request (refuel_request_status_id, created_at DESC), чтобы с помощью индекса можно было выполнять как основную фильтрацию, так и упорядочение. Вам также нужны индексы для столбцов, к которым вы присоединяетесь, но на данный момент эти таблицы маленькие и несущественные. В любом случае, индекс, который я предлагаю, на самом деле не сильно поможет с проблемами производительности, с которыми вы сталкиваетесь прямо сейчас. Вот несколько предложений:
- Не используйте
SELECT *
, если вам действительно не нужны все столбцы из всех таблиц, к которым вы присоединяетесь. Указание только необходимых столбцов означает, что postgres может загружать меньше данных в память и работать с ними быстрее. - Postgres тратит много времени на объединения, каждый раз объединяя около миллиона строк, когда вас действительно интересуют только десять из этих строк. Мы можем побудить его сначала выполнить порядок / ограничение, несколько изменив запрос:
WITH refuel_request_subset AS MATERIALIZED (
SELECT *
FROM refuel_request
WHERE refuel_request_status_id IN ('1', '2', '3')
ORDER BY created_at DESC
LIMIT 10
)
SELECT *
FROM refuel_request_subset AS refuel_request
LEFT OUTER JOIN user ON refuel_request.user_id = user.user_id
LEFT OUTER JOIN bill_qr ON refuel_request.bill_qr_id = bill_qr.bill_qr_id
LEFT OUTER JOIN car AS "order.car" ON refuel_request.car_id = "order.car".car_id
LEFT OUTER JOIN refuel_request_status ON refuel_request.refuel_request_status_id = refuel_request_status.refuel_request_status_id;
Примечание: Предполагается, что LEFT JOINS
он не будет добавлять строки в результирующий набор, как в случае с вашим текущим набором данных.
- Этот трюк действительно работает, только если у вас есть фиксированное количество идентификаторов, но вы можете выполнить
refuel_request_subset
запрос отдельно для каждого идентификатора, а затемUNION
результаты, в отличие от использованияIN
оператора. Это позволило бы postgres полностью использовать индекс, упомянутый выше.
Комментарии:
1. У вас не может быть индекса, который поддерживает как
WHERE
условие, так иORDER BY
в этом случае.2. @LaurenzAlbe Я знаю это и упомянул в предложении 3, что им нужно будет использовать a
UNION
для использования обоих столбцов индекса, аналогично тому, что вы ответили. Я думаю, я был недостаточно ясен.