Postgres МЕДЛЕННО, когда установлен LIMIT: как это исправить, кроме добавления фиктивного `ORDER BY`? - PullRequest
0 голосов
/ 07 февраля 2020

В Postgres, некоторые запросы намного медленнее при добавлении LIMIT:

Запросы:

SELECT * FROM review WHERE clicker_id=28 ORDER BY done DESC LIMIT 4; -- 51 sec
SELECT * FROM review WHERE clicker_id=28 ORDER BY id, done DESC LIMIT 4; -- 0.020s
SELECT * FROM review WHERE clicker_id=28 LIMIT 4; -- 0.007s
SELECT * FROM review WHERE clicker_id=28 ORDER BY id; -- 0.007s

Как видите, мне нужно добавить фиктивный идентификатор на ORDER BY для быстрого go. Я пытаюсь понять почему.

Запуск EXPLAIN на них:

EXPLAIN SELECT * FROM review WHERE clicker_id=28 ORDER BY done DESC LIMIT 4;
EXPLAIN SELECT * FROM review WHERE clicker_id=28 ORDER BY id, done DESC LIMIT 4;
EXPLAIN SELECT * FROM review WHERE clicker_id=28 LIMIT 4;
EXPLAIN SELECT * FROM review WHERE clicker_id=28 ORDER BY id;

дает это:

EXPLAIN SELECT * FROM review WHERE clicker_id=28 ORDER BY done DESC LIMIT 4
Limit  (cost=0.44..249.76 rows=4 width=56)
  ->  Index Scan using review_done on review  (cost=0.44..913081.13 rows=14649 width=56)
        Filter: (clicker_id = 28)


EXPLAIN SELECT * FROM review WHERE clicker_id=28 ORDER BY id, done DESC LIMIT 4
Limit  (cost=11970.75..11970.76 rows=4 width=56)
  ->  Sort  (cost=11970.75..12007.37 rows=14649 width=56)
        Sort Key: id, done DESC
        ->  Index Scan using review_clicker_id on review  (cost=0.44..11751.01 rows=14649 width=56)
              Index Cond: (clicker_id = 28)


EXPLAIN SELECT * FROM review WHERE clicker_id=28 LIMIT 4
Limit  (cost=0.44..3.65 rows=4 width=56)
  ->  Index Scan using review_clicker_id on review  (cost=0.44..11751.01 rows=14649 width=56)
        Index Cond: (clicker_id = 28)


EXPLAIN SELECT * FROM review WHERE clicker_id=28 ORDER BY id
Sort  (cost=12764.61..12801.24 rows=14649 width=56)
  Sort Key: id
  ->  Index Scan using review_clicker_id on review  (cost=0.44..11751.01 rows=14649 width=56)
        Index Cond: (clicker_id = 28)

Я не SQL эксперт, но я принимаю это Postgres ожидал, что запрос будет быстрее, чем на самом деле, и поэтому использовал способ извлечения данных, которые на самом деле неуместны, верно?

База данных:

  • Таблица review:
    • Содержит более 22 миллионов строк.
      • Данный пользователь получит 7 066 рядов вершин.
      • У одного в тесте (id 28) есть 288 в то время.
    • Имеет эта структура:
      • id: bigint Автоинкремент [nextval ('publi c .review_id_seq')]
      • тип: тип_оценки NULL
      • итерация: smallint NULL
      • повторений: smallint NULL
      • срок выполнения: timestamptz NULL
      • сделано: timestamptz NULL
      • добавлено: timestamptz NULL
      • clicker_id: bigint NULL
      • monologue_id: bigint NULL
    • Имеет следующие индексы:
      • УНИКАЛЬНЫЙ тип, clicker_id, monologue_id, итерация
      • INDEX clicker_id
      • ИНДЕКС выполнен, должный, monologue_id
      • ИНДЕКС id
      • ИНДЕКС выполнен DES C
      • ИНДЕКС тип

Дополнительные сведения:

Среда:

  • Запросы были выполнены при разработке с Postgres 9.6.14.
  • При запуске запросов в производство (Heroku Postgres, версия 9.6.16) разница менее драматична c, но все же невелика: медленные запросы могут занять 600 мс.

Переменная скорость:

  • Иногда одни и те же запросы (будь то точно такие же или для другого clicker_id) выполняются намного быстрее (менее 1 се c), но я не не понимаю почему. Мне нужно, чтобы они были последовательно быстрыми.
  • Если я использую LIMIT 288 для пользователя, который имеет 288 строк, то это будет намного быстрее (<1se c), но если я сделайте то же самое для пользователя, скажем, с 7066 строками, затем он снова станет очень медленным. </li>

До того, как я решил использовать пустышку ORDER BY, я попробовал следующее:

Ничего не помогло.

Вопрос:

Моя проблема сама по себе решена, но я недоволен ею:

  • Есть ли имя для этого "шаблона" , которое состоит из добавления фиктивной ORDER BY к ускорить процесс?
  • Как я могу обнаружить такие проблемы в будущем? (Чтобы понять это, потребовались годы.) Если я что-то пропустил, EXPLAIN не так уж и полезен:
    • Для медленного запроса стоимость обманчиво медленная, в то время как для быстрого варианта она обманчиво высока.
  • Альтернатива: Есть ли другой способ справиться с этим? (Потому что это решение похоже на взлом.)

Спасибо!


Подобные вопросы:

1 Ответ

1 голос
/ 07 февраля 2020

Основная проблема здесь заключается в том, что называется планом запроса на раннее прерывание. Вот нить хакеров pg sql, описывающая нечто подобное:

https://www.postgresql.org/message-id/541A2335.3060100%40agliodbs.com

Цитируя оттуда, именно поэтому планировщик использует часто-чрезвычайно медленное сканирование индекса, когда присутствует ORDER BY done DES C:

Как обычно, PostgreSQL значительно недооценивает n_distinct: он показывает chapters.user_id в 146 000 и отношение to_user_id: from_user_id как 1: 105 (в отличие от 1: 6, что о реальном соотношении). Это означает, что PostgreSQL считает, что может найти 20 строк в первых 2% индекса ... тогда как на самом деле ему нужно сканировать 50% индекса, чтобы найти их.

В вашем В этом случае планировщик полагает, что если он просто начнет проходить строки, упорядоченные по done des c (IOW, используя индекс review_done), он быстро найдет 4 строки с clicker_id = 28. Поскольку строки должны быть возвращены в порядке убывания «выполнено», он считает, что это сохранит шаг сортировки и будет быстрее, чем извлечение всех строк для кликера 28 и последующая сортировка. Учитывая реальное распределение строк, это часто может оказаться не так, требуя пропустить огромное количество строк, прежде чем найти 4 с clicker = 28.

Более общий способ обработки он должен использовать CTE (который в 9.6 по-прежнему остается ограничением оптимизации - это изменение в PG 12, FYI) для извлечения строк без упорядочения, а затем добавить ORDER BY во внешний запрос , Принимая во внимание, что выборка всех строк для пользователя, их сортировка и возвращение столько, сколько вам нужно, вполне разумны для вашего набора данных (даже кликера по 7 тыс. Строк не должно быть проблемой), вы можете помешать планировщику поверить в преждевременную отмену План будет самым быстрым, если в CTE не будет ORDER BY или LIMIT, что даст вам запрос типа , Однако я не уверен, есть ли название для этого шаблона.

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