Улучшает ли использование LIMIT производительность и заметно ли это?

11

Я хочу понять следующее.
Предположим, что у меня сложный запрос с, скажем, объединением 5 таблиц в группу по сумме и по порядку.
Оставляя в стороне какие-либо оптимизации самого запроса, например, индексы и т. Д.
Есть ли какое-либо существенное преимущество в производительности LIMIT? Я предполагаю, что весь запрос (и результаты) должен быть обработан до применения LIMIT, поэтому использование LIMIT для получения подмножества результатов дает ли это какое-либо значительное / заметное улучшение?

Джим
источник
2
Я предлагаю вам прочитать это для случаев, которые LIMITповышают эффективность: Оптимизация запросов LIMIT
ypercubeᵀᴹ

Ответы:

10

Если вы хотите воспользоваться преимуществами LIMITдля повышения производительности, вам нужно

  • понять данные, которые вы получаете
  • правильное индексирование правильной последовательности столбцов
  • взять на себя ответственность за рефакторинг запроса
  • используя LIMITпередJOIN

Эти принципы могут иметь большое значение, если вы можете их организовать.

Я изучил эти понятия, посмотрев это видео на YouTube (внимательно слушайте через французский акцент)

Я использовал эти концепции, чтобы ответить на очень сложный вопрос StackOverflow о получении 40 лучших статей из некоторых таблиц: 12 мая 2011 г .: извлечение отдельной строки из таблицы соединений .

В своем ответе на этот вопрос (16 мая 2011 г.) я написал следующий запрос и тщательно его протестировал:

SELECT
  AAA.author_id,
  AAA.date_created,
  IFNULL(BBB.title,'<NO_TITLE>') title,
  IFNULL(CCC.filename,'<NO-IMAGE>') filename,
  IFNULL(CCC.date_added,'<NO-IMAGE-DATE>') image_date
FROM
(
  SELECT
    AA.id,
    AA.date_added,
    BB.author_id,
    BB.date_created
  FROM
  (
    SELECT
      A.id,IFNULL(MAX(B.date_added),'1900-01-01 00:00:00') date_added
      FROM (SELECT id FROM articles ORDER BY date_created DESC LIMIT 40) A
      LEFT JOIN article_images B ON A.id = B.article_id
      GROUP BY A.id
  ) AA
  INNER JOIN articles BB USING (id)
) AAA
LEFT JOIN article_contents BBB ON AAA.id=BBB.article_id
LEFT JOIN article_images CCC
ON (AAA.id=CCC.article_id AND AAA.date_added=CCC.date_added)
ORDER BY AAA.date_created DESC;

Обратите внимание на строку в запросе с LIMIT

      FROM (SELECT id FROM articles ORDER BY date_created DESC LIMIT 40) A

Этот подзапрос скрыт на трех уровнях. Это позволило мне использовать последние 40 статей LIMIT. Затем я выполнил необходимые соединения после этого.

УРОКИ ВЫУЧЕНЫ

  • Выполнение LIMITвнутри подзапросов не всегда может быть ответом из-за количества индексов, содержимого данных и размера результирующего набора из LIMIT. Если у вас есть все ваши «утки подряд» (запомните четыре принципа для вашего запроса), вы можете получить удивительно хорошие результаты.
  • Сделайте ваши запросы максимально упрощенными LIMIT, собирая только ключи.
RolandoMySQLDBA
источник
Так (A [LEFT] JOIN B) LIMIT 100эквивалентно (A LIMIT 100) [LEFT] JOIN (B LIMIT 100)? Где [LEFT] JOINозначает внешнее или внутреннее соединение
Джим
Это больше похоже (A LIMIT 100) [LEFT] JOIN B. Идея состоит в том, чтобы использовать LIMITразмер результирующего набора как можно раньше. Я также использую LEFT JOINвместо того, INNER JOINпотому LEFT JOINчто сохранит порядок клавиш на левой стороне.
RolandoMySQLDBA
@ Джим Нет, это не так. Иногда они, как этот: (A LEFT JOIN B) GROUP BY A.pk LIMIT 100обычно могут быть переписаны как (A LIMIT 100) LEFT JOIN B GROUP BY A.pk(никакого ВНУТРЕННЕГО СОЕДИНЕНИЯ здесь, с внутренними объединениями они не будут эквивалентны.) Пример Роландо как раз такой случай.
ypercubeᵀᴹ
@ypercube: Значит, с внутренними объединениями что-то можно сделать, чтобы воспользоваться LIMIT?
Джим
Я имел в виду стратегию переписывания, изложенную Роландо. Также может быть полезен запрос с JOIN и LIMIT. Или нет. Это зависит.
ypercubeᵀᴹ
2

Когда запрос выполняется, он сначала переводится в план, состоящий из нескольких операторов. Существует два основных типа операторов: блокирующие и неблокирующие. Неблокирующий оператор извлекает строку (или несколько строк) из своего потомка или потомков для каждой строки, запрошенной из него. Оператор блокировки, с другой стороны, должен прочитать и обработать весь набор строк всех своих дочерних элементов, прежде чем он сможет произвести какой-либо вывод.

Сортировка является типичным оператором блокировки. Таким образом, отбор с заказом по не сильно выигрывает от лимита. Тем не менее, есть RDBMS, которые могут использовать алгоритм сортировки, который требует меньше памяти и быстрее, когда предоставляется условие limit. В этом случае достаточно просто сохранить текущие первые n строк и переместить их из памяти при появлении более ранних строк. Это может быть значительным приростом производительности. Однако я не уверен на 100%, что MySQL обладает такой способностью.

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

Себастьян Майн
источник
Я немного запутался с ответом. Вы упоминаете о сортировке, но группировка по сортам не так ли? Так, если, например, я удалил заказ по и придерживался группы по, применим ли ваш ответ? Или нужен другой анализ?
Джим
В зависимости от запроса и имеющихся индексов GROUP BYпотенциально может привести к тому, что план не содержит операторов блокировки.
Себастьян Майн
0

В моем случае я могу сказать Да , даже если я (все еще) не понимаю почему.

SELECT g0_.id AS id_0, COUNT(a1_.id_tarifs) AS sclr_1
FROM groupe_jardinerie g0_
INNER JOIN articles_tarifs a1_
  ON (a1_.groupe_jardinerie_id = g0_.id)
WHERE g0_.centrale_id = 511
  AND a1_.date_fin_tarif >= '2018-01-29 10:46:35'
GROUP BY g0_.id;

(result set)

8 rows in set (**18.14 sec**)

Обратите внимание на время: 18 секунд. Тот же запрос с большим ПРЕДЕЛОМ:

SELECT g0_.id AS id_0, COUNT(a1_.id_tarifs) AS sclr_1 
FROM groupe_jardinerie g0_
INNER JOIN articles_tarifs a1_
  ON (a1_.groupe_jardinerie_id = g0_.id)
WHERE g0_.centrale_id = 511 
  AND a1_.date_fin_tarif >= '2018-01-29 10:46:35'
GROUP BY g0_.id
LIMIT 100000000000;

(exact same result set)

8 rows in set (**1.32 sec**)

Более чем в десять раз быстрее !!!

EXPLAIN дают одинаковый результат для обоих запросов.

+----+-------------+-------+------------+--------+---------------------------------------------------+---------+---------+------------------------------+--------+----------+----------------------------------------------+
| id | select_type | table | partitions | type   | possible_keys                                     | key     | key_len | ref                          | rows   | filtered | Extra                                        |
+----+-------------+-------+------------+--------+---------------------------------------------------+---------+---------+------------------------------+--------+----------+----------------------------------------------+
|  1 | SIMPLE      | a1_   | NULL       | ALL    | IDX_438010BBC10784EF                              | NULL    | NULL    | NULL                         | 795135 |    33.33 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | g0_   | NULL       | eq_ref | PRIMARY,IDX_9CA5CF6758A1D71F,IDX_9CA5CF67670C757F | PRIMARY | 4       | phs.a1_.groupe_jardinerie_id |      1 |    50.00 | Using where                                  |
+----+-------------+-------+------------+--------+---------------------------------------------------+---------+---------+------------------------------+--------+----------+----------------------------------------------+

LIMIT должен вмешиваться только для ограничения набора результатов (т. Е. Если я выполню LIMIT 4, я получу только первые 4 строки из вышеуказанного набора результатов).

Пьер-Оливье Варес
источник
Страшно, какую версию вы используете и можете ли вы создать упрощенный контрольный пример?
Эван Кэрролл
1
Ваш ответ не доказывает никакой новой выгоды для LIMIT. Ваш первый запрос выполняется за 18 секунд, давая набор результатов. Все данные во втором запросе уже кэшируются в пуле буферов InnoDB из-за первого запроса, поэтому, конечно, второй запрос должен быть быстрее, даже если вы перезапустите mysql, выполните первый запрос, перезапустите mysql и запустите второй запрос, вы получите тот же результат. , Лучший результат для LIMITможет быть получен только от следующих действий: 1) LIMITдо JOIN, 2) LIMIT в порядке сортировки ASCили DESC.
RolandoMySQLDBA
Спасибо за ваш интерес. Создание упрощенного контрольного примера может быть затруднено.
Пьер-Оливье Варес