2009-08-07 11 views
21

Dostaję problemy z wydajnością podczas LIMIT ing MySQL SELECT z dużym offset:Jak przyspieszyć zapytanie MySQL z dużym przesunięciem w klauzuli LIMIT?

SELECT * FROM table LIMIT m, n; 

Jeśli offsetowej m jest, powiedzmy, większą niż 1.000.000, operacja jest bardzo powolny.

Muszę użyć limit m, n; Nie mogę użyć czegoś takiego jak id > 1,000,000 limit n.

Jak zoptymalizować to stwierdzenie, aby uzyskać lepszą wydajność?

Odpowiedz

13

Być może można utworzyć tabelę indeksowania, która zapewnia klucz sekwencyjny odnoszący się do klucza w tabeli docelowej. Następnie możesz dołączyć do tabeli indeksowania do tabeli docelowej i użyć klauzuli where, aby efektywniej uzyskać pożądane wiersze.

#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

podejście to działa tylko w instrukcjach, które nie zawierają warunku. moim zdaniem nie jest to dobre rozwiązanie. –

+3

Jak zaktualizować tabelę indeksu? W moim przypadku muszę zamówić przez kolumnę datetime i użyć dużych przesunięć powodujących powolne zapytania. Jeśli utworzę tę tabelę suportów, będę musiał ponownie wstawić za każdym razem, gdy mam nową datę, ponieważ nie jest ona w porządku. Już widzę to rozwiązanie, ale z tabelami tymczasowymi. –

9

Jest blogu gdzieś w internecie, w jaki sposób należy najlepiej dokonać wyboru wierszypokazać powinien być możliwie jak najmniejsze, a więc: tylko identyfikatory; i generowanie kompletnych wyników powinno z kolei pobrać wszystkie potrzebne dane tylko dla wybranych wierszy:.

Zatem SQL może być coś podobnego (niesprawdzone, nie jestem pewien, że to faktycznie zrobi nic dobrego):

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 

Jeśli silnik SQL jest zbyt prymitywny, aby umożliwić tego typu SQL, lub nie poprawia niczego, wbrew nadziei, warto byłoby podzielić to jedno zdanie na wiele instrukcji i uchwycić identyfikatory w strukturę danych.

Aktualizacja: Znalazłem blogu Mówiłam o: było Jeff Atwood na "All Abstractions Are Failed Abstractions" na Coding Horror.

+0

Przetestowałem Twój sugerowany SQL. ale nie robi żadnej poprawy. –

+1

Co jeśli masz klauzulę where na podstawie tabeli A? To nie zadziała, od pierwszego ograniczenia, a następnie zastosuj klauzulę where. Jeśli użyjesz join wewnątrz podzapytania, stracisz wydajność, prawda? –

+0

Dla mnie zadziałało, "SELECT id FROM ..." zapytanie zostało wykonane około 50 razy szybciej na zbiorze prawie miliona wierszy w porównaniu do 'SELECT bunch, of, fields FROM ...'. –

2

Odpowiedź Paula Dixona jest rzeczywiście rozwiązaniem problemu, ale musisz zachować tabelę sekwencji i upewnić się, że nie ma luki między wierszami.

Jeśli jest to wykonalne, lepszym rozwiązaniem byłoby po prostu upewnienie się, że oryginalna tabela nie ma luk między wierszami i rozpoczyna się od identyfikatora 1. Następnie pobierz wiersze, używając id dla stronicowania.

SELECT * FROM table A WHERE id > = 1 AND id < = 1000;
SELECT * FROM table A WHERE id > = 1001 AND id < = 2000;

i tak dalej ...

+0

WYBIERZ * Z tabeli WHERE id> 1000 LIMIT 1000 –

+1

Ponownie, nie zadziała, jeśli zastosowane są inne filtry. – devXen

2

Nie sądzę, nie ma żadnej potrzeby, aby utworzyć oddzielny indeks jeśli tabela ma już jeden. Jeśli tak, to można zamówić przez ten klucz podstawowy, a następnie użyć wartości klucza do kroku przez:

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

Innym optymalizacji byłoby nie używać SELECT * ale tylko identyfikator, dzięki czemu można go łatwo odczytać indeks i nie musi wtedy lokalizować wszystkich danych (zmniejszyć narzutu IO).Jeśli potrzebujesz innych kolumn, być może możesz dodać je do indeksu, tak aby były odczytywane kluczem podstawowym (który najprawdopodobniej będzie przechowywany w pamięci i dlatego nie wymaga sprawdzania dysku) - chociaż nie będzie to odpowiednie dla wszystkich przypadków, więc będziesz musiał grać.

napisałem artykuł o więcej szczegółów:

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

+0

Czy po prostu mysql lub większość dbs działa w ten dziwny sposób? Do tej pory najlepszym rozwiązaniem jest podzapytanie (gdy nie masz uporządkowanego indeksu). Najpierw zapytaj i zamów wszystko, a następnie wstaw przesunięcie. –

+0

Pomysł użycia tylko identyfikatora może być bardzo dobrym rozwiązaniem, zależy to od silnika pamięci, który przypuszczam! – twicejr

4

Jeśli rekordy są duże, powolność może być pochodzących z ładowania danych. Jeśli kolumna id jest indeksowana, to po prostu zaznaczenie jej będzie znacznie szybsze. Można następnie wykonać drugie zapytanie o klauzula dla odpowiednich identyfikatorów (lub można formułować A, gdzie zapis pomocą min i max IDS z pierwszego zapytania.)

powolny:

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

szybko:

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

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

Niedawno natknąłem się na ten problem. Problem polegał na dwóch częściach do naprawienia. Najpierw musiałem użyć wewnętrzna wybierz w moim klauzuli FROM, które zrobił mój ograniczania i kompensaty dla mnie na klucz podstawowy tylko:

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

Wtedy mógłbym użyć go jako z części mojego zapytania:

'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') 

Po pierwszym utworzeniu tego zapytania użyłem funkcji PRZESUNIĘCIE i LIMIT w MySql. To działało dobrze, dopóki nie przeszłam na stronie 100, wtedy przesunięcie zaczęło być nieznośnie powolne. Zmiana tego na BETWEEN w moim wewnętrznym zapytaniu przyspieszyła to dla dowolnej strony. Nie jestem pewien, dlaczego MySql nie przyspiesza OFFSET, ale między wydaje się ponownie go z powrotem.