2010-12-19 8 views
107

Szenario kurz: Eine Tabelle mit mehr als 16 Millionen Platten [2 GB groß]. Je höher LIMIT mit SELECT-Offset, desto langsamer ist die Abfrage wird bei der Verwendung von ORDER BY * primary_key *Warum verlangsamt MYSQL höheren LIMIT-Offset die Abfrage?

So

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

nimmt weit weniger als

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

dass nur Aufträge 30 Datensätze und gleiche in jedem Fall. Es ist also nicht der Overhead von ORDER BY.
Wenn nun die letzten 30 Zeilen abgerufen dauert es etwa 180 Sekunden. Wie kann ich diese einfache Abfrage optimieren?

+0

HINWEIS: Ich bin der Autor. In den obigen Fällen bezieht sich MySQL nicht auf den Index (PRIMARY). den folgenden Link von Benutzer "Quassnoi" zur Erklärung. – Rahman

+0

mögliches Duplikat von [Wie kann ich eine MySQL-Abfrage mit einem großen Offset in der LIMIT-Klausel beschleunigen?] (Http://stackoverflow.com/questions/1243952/how-can-i-speed-up-a-mysql- Query-mit-einem-großen-Offset-in-the-Limit-Klausel) –

Antwort

128

Es ist normal, dass höhere Offsets die Abfrage verlangsamen, da die Abfrage die ersten OFFSET + LIMIT Datensätze abzählen muss (und nur LIMIT von ihnen nehmen). Je höher dieser Wert ist, desto länger wird die Abfrage ausgeführt.

Die Abfrage kann nicht direkt zu OFFSET gehen, weil erstens die Datensätze unterschiedliche Länge haben können und zweitens Lücken aus gelöschten Datensätzen bestehen können. Es muss jeden Datensatz auf seinem Weg überprüfen und zählen.

Unter der Annahme, dass id ist ein PRIMARY KEY eine MyISAM Tabelle, können Sie es, indem man diesen Trick verwendet beschleunigen können:

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

diesen Artikel:

+5

MySQL "frühe Reihe Lookup" Verhalten war die Antwort, warum es so lange spricht.Durch den von Ihnen bereitgestellten Trick werden nur übereinstimmende IDs (direkt vom Index) gebunden, wodurch unnötige Zeilensuchen von zu vielen Datensätzen verhindert werden. Das hat den Trick gemacht, hurra! – Rahman

+1

Super ... gibt es irgendwelche Einschränkungen, wo dieser Trick nicht funktioniert? – aurora

+3

@harald: was genau meinst du mit "nicht arbeiten"? Dies ist eine reine Leistungsverbesserung. Wenn kein Index von ORDER BY verwendet werden kann oder der Index alle benötigten Felder abdeckt, benötigen Sie diese Problemumgehung nicht. – Quassnoi

4

Der zeitaufwendigste Teil der beiden Abfragen wird die Zeilen aus der Tabelle abruft. Logischerweise müssen in der LIMIT 0, 30 Version nur 30 Zeilen abgerufen werden. In der Version LIMIT 10000, 30 werden 10000 Zeilen ausgewertet und 30 Zeilen zurückgegeben. Es kann sein, können einige Optimierungs meine die Daten-Lesevorgang durchgeführt werden, aber bedenken Sie folgendes:

Was ist, wenn Sie in den Abfragen eine WHERE-Klausel hatte? Die Engine muss alle Zeilen zurückgeben, die qualifiziert sind, und dann die Daten sortieren und schließlich die 30 Zeilen abrufen.

betrachten auch den Fall, wo Zeilen werden nicht in der ORDER BY-Sequenz verarbeitet. Alle qualifizierenden Zeilen müssen sortiert werden, um zu bestimmen, welche Zeilen zurückgegeben werden sollen.

+1

nur wundern, warum es Zeit braucht, um diese 10000 Zeilen zu holen. Der für dieses Feld verwendete Index (id, der ein Primärschlüssel ist) sollte das Abrufen dieser Zeilen genauso schnell machen wie das Suchen nach dem PK-Index für die Datensatznummer. 10000, was wiederum so schnell sein soll, dass die Datei nach diesem Offset multipliziert mit der Indexaufzeichnungslänge gesucht wird (dh 10000 * 8 = Byte Nr. 80000 - 8 ist die Indexaufzeichnungslänge) – Rahman

+0

@Rahman - Das einzige So kannst du über die 10000 Zeilen hinaus zählen, indem du sie einzeln überspringst. Dies kann nur einen Index beinhalten, aber dennoch benötigen Indexzeilen Zeit, um durchzugehen. Es gibt _no_ MyISAM- oder InnoDB-Struktur, die korrekt (in allen Fällen) "suchen" kann, um 10000 aufzuzeichnen. Der 10000 * 8-Vorschlag geht von (1) MyISAM, (2) FIXED length record und (3) niemals von der Tabelle löscht . Anyway, MyISAM Indizes sind BTrees, also würde es nicht funktionieren. –

11

MySQL kann nicht direkt zum 10000. Datensatz (oder dem 80000. Byte als Vorschlag) gehen, weil es nicht davon ausgehen kann, dass es so gepackt/geordnet ist (oder kontinuierliche Werte von 1 bis 10000 hat). Obwohl es in Wirklichkeit so sein kann, kann MySQL nicht davon ausgehen, dass es keine Lücken/Lücken/gelöschten IDs gibt.

Also, wie Bobs bemerkt, muss MySQL 10000 Zeilen holen (oder durchlaufen 10000. Einträge des Index auf id), bevor die 30 zu finden, zurückzukehren.

EDIT: Zu meinen Punkt

Hinweis zeigen, dass, obwohl

SELECT * FROM large ORDER BY id LIMIT 10000, 30 

langsam sein würde (er),

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

wäre schnell (er) , und würde die gleichen Ergebnisse zurückgeben, vorausgesetzt, es gibt n o fehlt id s (d.h. Lücken).

+1

Das ist richtig. Aber da es durch "id" begrenzt ist, warum dauert es so lange, wenn diese ID in einem Index (Primärschlüssel) ist? Optimizer sollte direkt auf diesen Index verweisen und dann die Zeilen mit übereinstimmenden IDs (die von diesem Index stammten) abrufen. – Rahman

+1

Wenn Sie eine WHERE-Klausel für die ID verwendet haben, könnte sie direkt zu dieser Marke gehen. Wenn Sie jedoch ein Limit festlegen, das nach ID angeordnet ist, ist es nur ein relativer Gegenwert zum Anfang, also muss es den ganzen Weg durchlaufen. – Riedsio

119

I hatte genau das gleiche Problem selbst. In Anbetracht der Tatsache, dass Sie eine große Menge dieser Daten sammeln möchten und nicht einen bestimmten Satz von 30 werden Sie wahrscheinlich werden, um eine Schleife laufen und Erhöhen des Offset von 30.

Also, was Sie tun können, stattdessen ist:

  1. Halten Sie die letzte ID eines Satzes von Daten (30) (zB lastId = 530)
  2. die Bedingung hinzufügen WHERE id > lastId limit 0,30

So können Sie immer Offset eine NULL haben. Sie werden von der Leistungssteigerung begeistert sein.

+5

+1, verdient diese Antwort mehr Kredit – Alfie

+0

Funktioniert das, wenn es Lücken gibt? Was ist, wenn Sie keinen eindeutigen Schlüssel (zum Beispiel einen zusammengesetzten Schlüssel) haben? – xaisoft

+6

Es kann nicht für alle offensichtlich sein, dass dies nur funktioniert, wenn Ihre Ergebnismenge nach diesem Schlüssel in aufsteigender Reihenfolge sortiert ist (für absteigende Reihenfolge funktioniert die gleiche Idee, aber ändern Sie> lastid zu Eloff

3

Ich habe ein interessantes Beispiel gefunden, um SELECT Abfragen zu optimieren ORDER BY id LIMIT X, Y. Ich habe 35 Millionen Zeilen, also brauchte ich 2 Minuten, um eine Reihe von Zeilen zu finden. Hier

ist der Trick:

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

Setzen Sie einfach die WHERE mit der letzten id Sie viel, um die Leistung zu erhöhen bekommen. Für mich war es von 2 Minuten bis 1 Sekunde :)

Weitere interessante Tricks hier: http://www.iheavy.com/2013/06/19/3-ways-to-optimize-for-paging-in-mysql/

Es funktioniert auch mit Streichern

+0

dies funktioniert nur für Tabellen, in denen keine Daten gelöscht werden – miro

Verwandte Themen