2009-08-07 10 views
21

Sto ricevendo problemi di prestazioni quando LIMIT ing un mysql SELECT con una grande offset:Come posso velocizzare una query MySQL con un offset grande nella clausola LIMIT?

SELECT * FROM table LIMIT m, n; 

Se l'offset m è, diciamo, maggiore di 1.000.000, l'operazione è molto lenta.

Devo usare limit m, n; Non riesco a utilizzare qualcosa come id > 1,000,000 limit n.

Come ottimizzare questa affermazione per prestazioni migliori?

risposta

13

Forse potresti creare una tabella di indicizzazione che fornisce una chiave sequenziale relativa alla chiave nella tabella di destinazione. Quindi è possibile unire questa tabella di indicizzazione alla tabella di destinazione e utilizzare una clausola where per ottenere in modo più efficiente le righe desiderate.

#create table to store sequences 
CREATE TABLE seq (
    seq_no int not null auto_increment, 
    id int not null, 
    primary key(seq_no), 
    unique(id) 
); 

#create the sequence 
TRUNCATE seq; 
INSERT INTO seq (id) SELECT id FROM mytable ORDER BY id; 

#now get 1000 rows from offset 1000000 
SELECT mytable.* 
FROM mytable 
INNER JOIN seq USING(id) 
WHERE seq.seq_no BETWEEN 1000000 AND 1000999; 
+3

questo approccio funziona solo in istruzioni selezionate che non contengono la condizione. a mio parere non è una buona soluzione. –

+3

Come mantenere aggiornata questa tabella indice? Nel mio caso, devo ordinare per colonna datetime e utilizzare offset consistenti con query lente. Se creo questa tabella suport, dovrò reinserire ogni volta che ho una nuova data, poiché non è in ordine. Ho già visto questa soluzione, ma con tabelle temporanee. –

9

C'è un post sul blog da qualche parte su internet su come si dovrebbe meglio fare il selezione delle righe per mostrare dovrebbe essere il più compatto possibile, in tal modo: solo gli ID; e produrre i risultati completi dovrebbe a sua volta recuperare tutti i dati desiderati solo per le righe selezionate.

Così, la SQL potrebbe essere qualcosa di simile (non testata, non sono sicuro che in realtà farà nulla di buono):

select A.* from table A 
    inner join (select id from table order by whatever limit m, n) B 
    on A.id = B.id 
order by A.whatever 

Se il motore SQL è troppo primitiva per permettere questo tipo di istruzioni SQL, o non migliora nulla, contro la speranza, potrebbe essere utile rompere questa singola affermazione in più affermazioni e catturare gli id ​​in una struttura dati.

Aggiornamento: ho trovato il post sul blog di cui parlavo: era Jeff Atwood di "All Abstractions Are Failed Abstractions" su Coding Horror.

+0

Ho testato il tuo SQL suggerito. ma non fa alcun miglioramento. –

+1

Cosa succede se si dispone di una clausola where sulla tabella A? Non funzionerà, dal momento che limita prima, quindi applica la clausola where. Se usi il join all'interno della tua subquery, perderai le prestazioni, giusto? –

+0

Ha funzionato per me, la query 'SELECT id FROM ...' è stata eseguita circa 50 volte più velocemente su un set di quasi un milione di righe rispetto a 'SELECT bunch, of, fields FROM ...'. –

2

La risposta di Paul Dixon è davvero una soluzione al problema, ma dovrai mantenere la tabella delle sequenze e accertarti che non ci siano spazi vuoti tra le righe.

Se ciò è possibile, una soluzione migliore sarebbe semplicemente assicurarsi che la tabella originale non abbia spazi vuoti tra le righe e inizi dall'id 1. Quindi prendi le righe usando l'id per l'impaginazione.

SELEZIONA * DA tabella A DOVE id > = 1 E id < = 1000;
SELEZIONA * DA tabella A DOVE id > = 1001 AND id < = 2000;

e così via ...

+0

SELEZIONA * DA tabella WHERE id> 1000 LIMIT 1000 –

+1

Anche in questo caso, non funzionerà se vengono applicati altri filtri. – devXen

2

Io non credo che ci sia alcuna necessità di creare un indice separato se la tabella ha già uno. Se è così, allora si può ordinare da questa chiave primaria e quindi utilizzare i valori della chiave per passare:

SELECT * FROM myBigTable WHERE id > :OFFSET ORDER BY id ASC; 

Un'altra ottimizzazione sarebbe non utilizzare SELECT *, ma solo l'ID in modo che possa semplicemente leggere l'indice e non deve quindi individuare tutti i dati (ridurre l'overhead di I/O).Se hai bisogno di alcune delle altre colonne, forse potresti aggiungerle all'indice in modo che vengano letti con la chiave primaria (che sarà probabilmente conservata in memoria e quindi non richiederà una ricerca del disco) - anche se questo non è appropriato per tutti i casi, quindi dovrai giocare.

ho scritto un articolo con maggiori dettagli:

http://www.4pmp.com/2010/02/scalable-mysql-avoid-offset-for-large-tables/

+0

È solo mysql o mosts dbs che agisce in questo modo strano? Finora, la soluzione migliore è la sottoquery (quando non si ha un indice ordinato). Interrogare e ordinare tutto per primo, quindi inserire l'offset. –

+0

L'idea di utilizzare solo l'ID potrebbe essere un'ottima soluzione, dipende dal motore di archiviazione, suppongo! – twicejr

4

Se i record sono di grandi dimensioni, la lentezza può venire dal caricamento dei dati. Se la colonna id è indicizzata, la semplice selezione sarà molto più veloce. È quindi possibile fare una seconda query con una clausola IN per gli ID appropriati (o potrebbe formulare una clausola WHERE con il min e ids max dalla prima query.)

lento:

SELECT * FROM table ORDER BY id DESC LIMIT 10 OFFSET 50000 

veloce:

SELECT id FROM table ORDER BY id DESC LIMIT 10 OFFSET 50000 

SELECT * FROM table WHERE id IN (1,2,3...10) 
0

Ho incontrato di recente questo problema. Il problema consisteva in due parti da risolvere. Per prima cosa ho dovuto usare una di selezione interna nella mia clausola FROM che ha fatto il mio limitante e compensazione per me sulla chiave primaria unica:

$subQuery = DB::raw("(SELECT id FROM titles WHERE id BETWEEN {$startId} AND {$endId} ORDER BY title) as t"); 

quindi ho potuto utilizzare che come da parte della mia interrogazione:

'titles.id', 
          'title_eisbns_concat.eisbns_concat', 
          'titles.pub_symbol', 
          'titles.title', 
          'titles.subtitle', 
          'titles.contributor1', 
          'titles.publisher', 
          'titles.epub_date', 
          'titles.ebook_price', 
          'publisher_licenses.id as pub_license_id', 
          'license_types.shortname', 
          $coversQuery 
         ) 
         ->from($subQuery) 
         ->leftJoin('titles', 't.id', '=', 'titles.id') 
         ->leftJoin('organizations', 'organizations.symbol', '=', 'titles.pub_symbol') 
         ->leftJoin('title_eisbns_concat', 'titles.id', '=', 'title_eisbns_concat.title_id') 
         ->leftJoin('publisher_licenses', 'publisher_licenses.org_id', '=', 'organizations.id') 
         ->leftJoin('license_types', 'license_types.id', '=', 'publisher_licenses.license_type_id') 

La prima volta che ho creato questa query avevo usato OFFSET e LIMIT in MySql. Questo ha funzionato bene fino a quando ho superato pagina 100, quindi l'offset ha iniziato a diventare insopportabilmente lento. Cambiarlo in BETWEEN nella mia query interna lo ha accelerato per qualsiasi pagina. Non sono sicuro del motivo per cui MySql non ha accelerato lo OFFSET ma tra sembra riavvolgerlo.

Problemi correlati