Чому зсув MYSQL вищої LIMIT сповільнює запит?


173

Короткий сценарій: Таблиця з більш ніж 16 мільйонами записів [розміром 2 ГБ]. Чим вище зсув LIMIT за SELECT, тим повільніше стає запит при використанні ЗАМОВЛЕННЯ ДО * basic_key *

Так

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

займає набагато менше, ніж

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

Це замовляє лише 30 записів і все одно. Отже, це не накладні витрати від ORDER BY.
Тепер, коли ви отримуєте останні 30 рядків, це займає близько 180 секунд. Як я можу оптимізувати цей простий запит?


ПРИМІТКА: Я автор. У вищезазначених випадках 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

Дивіться цю статтю:


7
Поведінка "раннього пошуку рядків" MySQL була відповіддю, чому так довго говорять. За наданим вами трюком пов'язані лише відповідні ID (безпосередньо за індексом), що зберігає непотрібні пошукові рядки занадто багато записів. Це зробив трюк, ура!
Рахман

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

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


Чи працює це, якщо є прогалини? Що робити, якщо у вас немає жодного унікального ключа (наприклад, складеного ключа)?
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 для повернення.

EDIT : Для ілюстрації моєї точки зору

Зауважимо, що хоча

SELECT * FROM large ORDER BY id LIMIT 10000, 30 

буде повільним (ер) ,

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

буде швидко (er) , і повертає ті самі результати за умови відсутності ids (тобто прогалин).


2
Це вірно. Але оскільки він обмежений "id", чому це займає так довго, коли цей id знаходиться в індексі (первинний ключ)? Оптимізатор повинен прямо звертатися до цього індексу, а потім отримувати рядки зі збіганими ідентифікаторами (які виходили з цього індексу)
Рахман

1
Якщо ви використовували пропозицію WHERE на id, воно може перейти до цієї позначки. Однак, якщо ви поставите обмеження на нього, упорядкований ідентифікатором, це лише відносне протилежне до початку, тому воно повинно пройти весь шлях.
Рієдсіо

Дуже хороша стаття eversql.com/…
Pažout

Працював для мене @Riedsio Дякую.
mahesh kajale

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/

Він також працює з рядками


1
це працює лише для таблиць, де дані не видаляються
miro

1
@miro Це справедливо лише в тому випадку, якщо ви працюєте з припущенням, що ваш запит може робити пошук на випадкових сторінках, що я не вірю в цей плакат. Хоча мені не подобається цей метод для більшості справ у реальному світі, він буде працювати з прогалинами, якщо ви завжди базуєте його на останньому отриманому ідентифікаторі.
Греміо

5

Частка двох запитів, що займає багато часу, - це отримання рядків із таблиці. Логічно кажучи, у LIMIT 0, 30версії потрібно отримати лише 30 рядків. У LIMIT 10000, 30версії оцінюється 10000 рядків і повертається 30 рядків. Може бути проведена деяка оптимізація мого процесу зчитування даних, але врахуйте наступне:

Що робити, якщо у запитах було слово WHERE? Двигун повинен повернути всі рядки, які відповідають вимогам, а потім сортувати дані та нарешті отримати 30 рядків.

Також розглянемо випадок, коли рядки не обробляються в послідовності ЗАМОВЛЕННЯ ПО. Усі рядки, що відповідають вимогам, повинні бути відсортовані, щоб визначити, які рядки повертати.


1
просто цікаво, чому для отримання цих 10000 рядків потрібен час. Індекс, що використовується в цьому полі (id, який є первинним ключем), повинен отримувати ці рядки так само швидко, як і пошук цього індексу PK для запису №. 10000, що, в свою чергу, має бути швидким як пошук файлу до цього зміщення, помножене на довжину запису індексу, (тобто, пошук 10000 * 8 = байт немає 80000 - враховуючи, що 8 - довжина запису індексу)
Рахман

@Rahman - Єдиний спосіб перерахувати 10000 рядків - це переходити один за одним. Це може просто включати в себе індекс, але все ж рядки індексу потребують часу, щоб перейти. Там немає ні MyISAM або структури InnoDB , які можуть правильно (у всіх випадках) «шукати» , щоб записати 10000. 10000 * 8 пропозиція передбачає (1) MyISAM, (2) FIXED довжиною записи, і (3) ніколи ніяких вилучень з таблиці . У будь-якому випадку, індекси MyISAM - це BTrees, тому це не працюватиме.
Рік Джеймс

Як я вважаю, у цій відповіді я вважаю, що дуже повільна частина - це пошук рядків, а не обхід індексів (що, звичайно, також буде додаватися, але ніде не так багато, як пошук рядків на диску). Виходячи з обхідних запитів, передбачених для цього питання, я вважаю, що пошуки рядків мають місце, якщо ви вибираєте стовпці поза індексом - навіть якщо вони не є частиною замовлення за пунктом або де. Я не знайшов причини, чому це необхідно, але, здається, чомусь деякі шляхи вирішення допомагають.
Греміо

1

Для тих, хто цікавиться порівнянням та цифрами :)

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

  • Синій: = SELECT * FROM post ORDER BY id LIMIT {offset}, 5
  • Помаранчевий: = метод @ Quassnoi. 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.

  • зелений: = синій раніше
  • червоний: = помаранчевий раніше

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.