Perché l'offset LIMIT più alto di MYSQL rallenta la query?


173

Scenario in breve: una tabella con oltre 16 milioni di record [2 GB di dimensione]. Maggiore è l'offset LIMIT con SELEZIONA, più lenta diventa la query quando si utilizza ORDER BY * primary_key *

Così

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

richiede molto meno di

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

Questo ordina solo 30 record e lo stesso comunque. Quindi non è il sovraccarico di ORDER BY.
Ora quando si recuperano le ultime 30 righe ci vogliono circa 180 secondi. Come posso ottimizzare quella semplice query?


NOTA: sono l'autore. MySQL non si riferisce all'indice (PRIMARY) nei casi precedenti. vedere il link seguente dall'utente "Quassnoi" per una spiegazione.
Rahman,

Risposte:


197

È normale che offset più alti rallentino la query, poiché la query deve contare i primi OFFSET + LIMITrecord (e prenderne solo LIMIT). Maggiore è questo valore, più lunga sarà la query.

La query non può andare a destra OFFSET perché, in primo luogo, i record possono avere una lunghezza diversa e, in secondo luogo, possono esserci spazi vuoti dai record eliminati. Deve controllare e contare ogni record nel suo cammino.

Supponendo che idsia uno PRIMARY KEYdi un MyISAMtavolo, puoi accelerarlo usando questo trucco:

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

Vedi questo articolo:


7
Il comportamento di MySQL "Early Row Lookup" è stata la risposta per cui parla così a lungo. Con il trucco fornito, vengono associati solo gli ID corrispondenti (direttamente dall'indice), salvando le ricerche di righe non necessarie di troppi record. Questo ha funzionato, evviva!
Rahman,

4
@harald: cosa intendi esattamente con "non funziona"? Questo è un puro miglioramento delle prestazioni. Se non esiste un indice utilizzabile da ORDER BYo l'indice copre tutti i campi necessari, non è necessaria questa soluzione alternativa.
Quassnoi,

6
@ f055: la risposta dice "accelerare", non "rendere istantaneo". Hai letto la prima frase della risposta?
Quassnoi,

3
È possibile eseguire qualcosa di simile per InnoDB?
NeverEndingQueue,

3
@Lanti: si prega di pubblicarlo come domanda separata e non dimenticare di taggarlo postgresql. Questa è una risposta specifica per MySQL.
Quassnoi,

220

Anch'io ho avuto lo stesso problema. Dato che vuoi raccogliere una grande quantità di questi dati e non un insieme specifico di 30, probabilmente eseguirai un ciclo e incrementerai l'offset di 30.

Quindi quello che puoi fare invece è:

  1. Conserva l'ultimo ID di un set di dati (30) (ad esempio lastId = 530)
  2. Aggiungi la condizione WHERE id > lastId limit 0,30

Quindi puoi sempre avere un offset ZERO. Rimarrai stupito dal miglioramento delle prestazioni.


Funziona se ci sono lacune? Cosa succede se non si dispone di un'unica chiave univoca (ad esempio una chiave composita)?
xaisoft,

8
Potrebbe non essere ovvio a tutto ciò che funziona solo se il set di risultati è ordinato in base a quella chiave, in ordine crescente (per l'ordine decrescente funziona la stessa idea, ma cambia> lastid in <lastid.) Non importa se è il chiave primaria o un altro campo (o gruppo di campi.)
Eloff

Ben fatto quell'uomo! Una soluzione molto semplice che ha risolto il mio problema :-)
oodavid,

30
Solo una nota che il limite / offset è spesso usato nei risultati impaginati e tenere lastId semplicemente non è probabilmente perché l'utente può saltare a qualsiasi pagina, non sempre alla pagina successiva. In altre parole, l'offset spesso deve essere calcolato dinamicamente in base alla pagina e al limite, anziché seguire un modello continuo.
Tom,

3
Parlo più a lungo di "ricordare da dove eri rimasto" in mysql.rjweb.org/doc.php/pagination
Rick James,

17

MySQL non può andare direttamente al 10000 ° record (o all'80000 ° byte come da te suggerito) perché non può presumere che sia compresso / ordinato in questo modo (o che abbia valori continui da 1 a 10000). Anche se in realtà potrebbe essere così, MySQL non può presumere che non ci siano buchi / lacune / ID cancellati.

Quindi, come notato da bob, MySQL dovrà recuperare 10000 righe (o attraversare oltre 10000 voci dell'indice su id) prima di trovare il 30 da restituire.

EDIT : per illustrare il mio punto

Si noti che sebbene

SELECT * FROM large ORDER BY id LIMIT 10000, 30 

sarebbe lento (er) ,

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

sarebbe veloce (er) e restituirebbe gli stessi risultati a condizione che non vi siano ids mancanti (ovvero vuoti).


2
Questo è corretto. Ma poiché è limitato da "id", perché ci vuole così tanto tempo quando quell'id è all'interno di un indice (chiave primaria)? L'ottimizzatore dovrebbe fare riferimento direttamente a quell'indice e quindi recuperare le righe con ID corrispondenti (che provenivano da tale indice)
Rahman,

1
Se hai usato una clausola WHERE su id, potrebbe andare proprio a quel segno. Tuttavia, se ci metti un limite, ordinato per id, è solo un contatore relativo all'inizio, quindi deve attraversare tutto il percorso.
Riedsio,

Ottimo articolo eversql.com/…
Pažout

Ha funzionato per me @Riedsio Grazie.
Mahesh Kajale,

8

Ho trovato un esempio interessante per ottimizzare le query SELECT ORDER BY id LIMIT X, Y. Ho 35 milioni di file, quindi ci sono voluti circa 2 minuti per trovare un intervallo di file.

Ecco il trucco:

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

Basta inserire WHERE con l'ultimo ID ottenuto per aumentare notevolmente le prestazioni. Per me è stato da 2 minuti a 1 secondo :)

Altri trucchi interessanti qui: http://www.iheavy.com/2013/06/19/3-ways-to-optimize-for-paging-in-mysql/

Funziona anche con le stringhe


1
funziona solo per le tabelle, dove non vengono cancellati dati
miro,

1
@miro Questo è vero solo se stai lavorando partendo dal presupposto che la tua query può fare ricerche su pagine casuali, cosa che non credo stia presupponendo questo poster. Anche se non mi piace questo metodo per la maggior parte dei casi del mondo reale, questo funzionerà con lacune fintanto che lo si basa sempre sull'ultimo ID ottenuto.
Gremio,

5

La parte che richiede tempo delle due query sta recuperando le righe dalla tabella. Logicamente parlando, nella LIMIT 0, 30versione, è necessario recuperare solo 30 righe. Nella LIMIT 10000, 30versione, vengono valutate 10000 righe e vengono restituite 30 righe. Ci può essere un po 'di ottimizzazione nel mio processo di lettura dei dati, ma considera quanto segue:

E se avessi una clausola WHERE nelle query? Il motore deve restituire tutte le righe qualificate, quindi ordinare i dati e infine ottenere le 30 righe.

Considera anche il caso in cui le righe non vengono elaborate nella sequenza ORDER BY. Tutte le righe qualificanti devono essere ordinate per determinare quali righe restituire.


1
chiedo solo perché ci vuole tempo per recuperare quelle 10000 righe. L'indice utilizzato su quel campo (id, che è una chiave primaria) dovrebbe rendere il recupero di quelle righe più veloce della ricerca di quell'indice PK per il record n. 10000, che a sua volta dovrebbe essere veloce come la ricerca del file a quell'offset moltiplicato per la lunghezza del record dell'indice, (cioè, cercando 10000 * 8 = byte n. 80000 - dato che 8 è la lunghezza del record dell'indice)
Rahman

@Rahman - L'unico modo per contare oltre le 10000 righe è di superarle una per una. Ciò può comportare solo un indice, ma le righe dell'indice richiedono ancora tempo per essere esaminate. Non esiste una struttura MyISAM o InnoDB che possa correttamente (in tutti i casi) "cercare" di registrare 10000. Il suggerimento 10000 * 8 presuppone (1) MyISAM, (2) FIXED length record e (3) mai nessuna eliminazione dalla tabella . Ad ogni modo, gli indici MyISAM sono BTree, quindi non funzionerebbe.
Rick James,

Come affermato da questa risposta, credo, la parte veramente lenta è la ricerca di righe, non attraversando gli indici (che ovviamente aggiungeranno anche, ma in nessun luogo vicino quanto le ricerche di riga su disco). Sulla base delle query alternative fornite per questo problema, credo che le ricerche di riga tendano a verificarsi se si selezionano colonne all'esterno dell'indice, anche se non fanno parte dell'ordine di o dove clausola. Non ho trovato un motivo per cui questo è necessario, ma sembra essere il motivo per cui alcune soluzioni alternative aiutano.
Gremio,

1

Per coloro che sono interessati a un confronto e cifre :)

Esperimento 1: il set di dati contiene circa 100 milioni di righe. Ogni riga contiene diversi campi BIGINT, TINYINT e due campi TEXT (deliberatamente) contenenti circa 1k caratteri.

  • Blu: = SELECT * FROM post ORDER BY id LIMIT {offset}, 5
  • Arancione: = metodo di @ Quassnoi. SELECT t.* FROM (SELECT id FROM post ORDER BY id LIMIT {offset}, 5) AS q JOIN post t ON t.id = q.id
  • Naturalmente, il terzo metodo ... WHERE id>xxx LIMIT 0,5non appare qui poiché dovrebbe essere un tempo costante.

Esperimento 2: cosa simile, tranne per il fatto che una riga ha solo 3 GRANDI.

  • verde: = il blu prima
  • rosso: = l'arancione prima

inserisci qui la descrizione dell'immagine

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.