Почему MYSQL с более высоким смещением LIMIT замедляет запрос?

173

Краткий сценарий: таблица с более чем 16 миллионами записей [размером 2 ГБ]. Чем выше смещение LIMIT в SELECT, тем медленнее становится запрос при использовании ORDER BY * primary_key *

Так

SELECT * FROM large ORDER BY `id`  LIMIT 0, 30 

занимает гораздо меньше, чем

SELECT * FROM large ORDER BY `id` LIMIT 10000, 30 

Это только заказывает 30 записей и то же самое так или иначе. Так что это не накладные расходы от ORDER BY.
Теперь при получении последних 30 строк это занимает около 180 секунд. Как я могу оптимизировать этот простой запрос?

Rahman
источник
ПРИМЕЧАНИЕ: я автор. MySQL не ссылается на индекс (PRIMARY) в вышеупомянутых случаях. см. ниже ссылку пользователя "Quassnoi" для объяснения.
Рахман

Ответы:

197

Обычно старшие смещения замедляют запрос, так как запрос должен отсчитывать первые OFFSET + LIMITзаписи (и принимать только LIMITих). Чем выше это значение, тем дольше выполняется запрос.

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

Предполагая , что idявляется PRIMARY KEYиз MyISAMтаблицы, вы можете ускорить его, используя этот трюк:

SELECT  t.*
FROM    (
        SELECT  id
        FROM    mytable
        ORDER BY
                id
        LIMIT 10000, 30
        ) q
JOIN    mytable t
ON      t.id = q.id

Смотрите эту статью:

Quassnoi
источник
7
Поведение «раннего поиска строк» ​​в MySQL было ответом, почему он так долго говорит. Согласно предоставленному трюку, привязываются только совпадающие идентификаторы (непосредственно по индексу), сохраняя ненужные поиски строк слишком большого количества записей. Это добилось цели, ура!
Рахман
4
@harald: что именно ты подразумеваешь под "не работает"? Это чистое улучшение производительности. Если нет индекса, который можно использовать, ORDER BYили он охватывает все необходимые поля, этот обходной путь не требуется.
Quassnoi
6
@ f055: ответ говорит: «ускорись», а не «сделай моментально». Вы читали самое первое предложение ответа?
Quassnoi
3
Можно ли запустить что-то подобное для InnoDB?
NeverEndingQueue
3
@Lanti: пожалуйста, опубликуйте его как отдельный вопрос и не забудьте пометить его postgresql. Это специфичный для MySQL ответ.
Quassnoi
220

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

Итак, что вы можете сделать вместо этого:

  1. Содержит последний идентификатор набора данных (30) (например, lastId = 530)
  2. Добавить условие WHERE id > lastId limit 0,30

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

Никос Кыр
источник
Это работает, если есть пробелы? Что если у вас нет ни одного уникального ключа (например, составного ключа)?
xaisoft
8
Для всех может быть неочевидным, что это работает, только если ваш набор результатов отсортирован по этому ключу в порядке возрастания (для нисходящего порядка работает та же идея, но измените> lastid на <lastid.) Не имеет значения, если это первичный ключ или другое поле (или группа полей)
Eloff
Молодец этот человек! Очень простое решение, которое решило мою проблему :-)
oodavid
30
Просто обратите внимание, что предел / смещение часто используется в постраничных результатах, и удержание lastId просто невозможно, потому что пользователь может перейти на любую страницу, а не всегда на следующую. Другими словами, смещение часто нужно рассчитывать динамически на основе страницы и лимита, а не по непрерывному шаблону.
Том
3
Я более подробно говорю о «запоминании того, где вы остановились» в mysql.rjweb.org/doc.php/pagination
Рик Джеймс
17

MySQL не может перейти непосредственно к 10000-й записи (или 80000-му байту, как вы предлагаете), потому что он не может предположить, что он упакован / упорядочен таким образом (или что он имеет непрерывные значения от 1 до 10000). Хотя это может быть так в действительности, MySQL не может предположить, что нет дыр / пробелов / удаленных идентификаторов.

Таким образом, как заметил Бобс, MySQL должен будет извлечь 10000 строк (или пройти через 10000-ые записи индекса id), прежде чем найти 30 для возврата.

РЕДАКТИРОВАТЬ : Чтобы проиллюстрировать мою точку зрения

Обратите внимание, что хотя

SELECT * FROM large ORDER BY id LIMIT 10000, 30 

будет медленно (э) ,

SELECT * FROM large WHERE id >  10000 ORDER BY id LIMIT 30 

будет быстрым (er) и будет возвращать те же результаты при условии отсутствия пропущенных ids (т. е. пропусков).

Riedsio
источник
2
Это верно. Но так как он ограничен «id», почему это занимает так много времени, когда этот id находится в индексе (первичный ключ)? Оптимизатор должен ссылаться на этот индекс напрямую, а затем извлекать строки с совпадающими идентификаторами (которые получены из этого индекса)
Рахман
1
Если вы использовали предложение WHERE для идентификатора, оно может перейти прямо к этой отметке. Однако, если вы наложите на него ограничение, упорядоченное по id, это будет просто относительный счетчик к началу, поэтому он должен проходить весь путь.
Ридсио
Очень хорошая статья eversql.com/…
Pažout
Работал для меня @Riedsio Спасибо.
Махеш Каджале
8

Я нашел интересный пример для оптимизации запросов SELECT ORDER BY id LIMIT X, Y. У меня 35 миллионов строк, поэтому мне понадобилось около 2 минут, чтобы найти диапазон строк.

Вот хитрость:

select id, name, address, phone
FROM customers
WHERE id > 990
ORDER BY id LIMIT 1000;

Просто поместите WHERE с последним идентификатором, который вы получили, чтобы увеличить производительность. Для меня это было от 2 минут до 1 секунды :)

Другие интересные трюки здесь: http://www.iheavy.com/2013/06/19/3-ways-to-optimize-for-paging-in-mysql/

Это работает также со строками

SYM
источник
1
это работает только для таблиц, где данные не удаляются
miro
1
@miro Это верно только в том случае, если вы работаете в предположении, что ваш запрос может выполнять поиск на случайных страницах, что я не думаю, что этот постер предполагает. Хотя мне не нравится этот метод для большинства реальных случаев, он будет работать с пробелами, если вы всегда основываете его на последнем полученном идентификаторе.
Гремио
5

Отнимающая много времени часть двух запросов извлекает строки из таблицы. Логически говоря, в LIMIT 0, 30версии нужно извлечь только 30 строк. В LIMIT 10000, 30версии оценивается 10000 строк и возвращается 30 строк. Может быть некоторая оптимизация может быть сделана в процессе чтения данных, но учтите следующее:

Что если в запросах есть предложение WHERE? Движок должен вернуть все подходящие строки, а затем отсортировать данные и, наконец, получить 30 строк.

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

качается
источник
1
просто интересно, почему это занимает время, чтобы получить эти 10000 строк. Индекс, используемый в этом поле (id, который является первичным ключом), должен ускорять поиск этих строк так же быстро, как поиск этого PK-индекса для записи №. 10000, что, в свою очередь, должно быть быстрым, как поиск файла с этим смещением, умноженным на длину записи индекса (т. Е. Поиск 10000 * 8 = байт № 80000 - учитывая, что 8 - длина записи индекса)
Rahman
@Rahman - Единственный способ сосчитать 10000 строк - это перешагнуть через них один за другим. Это может просто включать индекс, но все же строки индекса требуют времени для перехода. Там нет нет MyISAM или структуры InnoDB которые могут правильно (во всех случаях) «искать» , чтобы записать 10000. 10000 * 8 предложение предполагает (1) MyISAM, (2) ИСПРАВЛЕНО длиной записи, и (3) никогда никаких удалений из таблицы , Во всяком случае, индексы MyISAM являются BTrees, поэтому он не будет работать.
Рик Джеймс
Как было сказано в этом ответе, я полагаю, что очень медленной частью является поиск строк, а не обход индексов (что, конечно, тоже сложится, но далеко не так много, как поиск строк на диске). Основываясь на обходных запросах, предоставленных для этой проблемы, я полагаю, что поиск строк, как правило, происходит, если вы выбираете столбцы вне индекса - даже если они не являются частью порядка by или where. Я не нашел причину, почему это необходимо, но похоже, что некоторые из обходных путей помогают.
Гремио
1

Для тех, кому интересно сравнение и цифры :)

Эксперимент 1: набор данных содержит около 100 миллионов строк. Каждая строка содержит несколько полей BIGINT, TINYINT, а также два поля TEXT (намеренно), содержащие около 1 тыс. Символов.

  • Синий: = SELECT * FROM post ORDER BY id LIMIT {offset}, 5
  • Оранжевый: = метод @ Кассной. SELECT t.* FROM (SELECT id FROM post ORDER BY id LIMIT {offset}, 5) AS q JOIN post t ON t.id = q.id
  • Конечно, третий способ ... WHERE id>xxx LIMIT 0,5здесь не фигурирует, поскольку он должен быть постоянным.

Эксперимент 2: похожая вещь, за исключением того, что в одной строке есть только 3 BIGINT.

  • зеленый: = синий перед
  • красный: = оранжевый перед

введите описание изображения здесь

ch271828n
источник