2009-10-20 12 views
9

Tak więc mam tabelę z ponad 80 000 rekordów, ten nazywa się system. Mam też inny stół o nazwie poniżej.Optymalizowanie mojego oświadczenia mysql! - RAND() ZBYT WOLNO

Potrzebuję mojego wyciągu, aby losowo wybrać rekordy z tabeli systemowej, gdzie ten identyfikator nie jest już wymieniony w poniższej tabeli pod bieżącym identyfikatorem użytkownika.

Więc tutaj jest to, co mam:

SELECT system.id, 
      system.username, 
      system.password, 
      system.followed, 
      system.isvalid, 
      follows.userid, 
      follows.systemid 
     FROM system 
    LEFT JOIN follows ON system.id = follows.systemid 
        AND follows.userid = 2 
     WHERE system.followed = 0 
     AND system.isvalid = 1 
     AND follows.systemid IS NULL 
    ORDER BY RAND() 
     LIMIT 200 

Teraz wotks doskonale, z wyjątkiem, że trwa o całą minutę, zanim będzie mógł nawet rozpocząć przetwarzanie zadania w parze z ewidencji to wybranych. W tym czasie scenariusz zazwyczaj nie działa, a nic się nie dzieje.

Czy ktoś może mi pokazać, jak przerobić to, więc ten sam pomysł został zrobiony, ale nie używa on kolejności według rand? Wydaje się to spowalniać całą masę.

Dzięki!

+1

Jakie wskaźniki są dostępne w polach JOIN? To może być wielka szyjka butelki. – dnagirl

+0

Nie jestem zbyt pewny, co masz na myśli ... – Brandon

+0

@Brandon Wiem, że jest to trochę za późno, ale jeśli chcesz zrobić na wpół uproszczony sposób, możesz umieścić go w podkwerendie ... zobacz moją odpowiedź tutaj dla więcej szczegółów http://stackoverflow.com/questions/25361158/mysql-select-random-on-large-table-order-by-score/25364339?noredirect=1#comment39644652_25364339 –

Odpowiedz

7

Nie jestem pewien, czy istnieje proste rozwiązanie, aby zastąpić zapytanie, oto artykuł na temat korekty tego typu problemu.

http://www.titov.net/2005/09/21/do-not-use-order-by-rand-or-how-to-get-random-rows-from-table/

+0

Dzięki, ale to nie jest realna opcja na sposób to zapytanie działa. – Brandon

+0

Dlaczego nie? Istnieje wiele różnych rozwiązań w tym artykule, z których niektóre, jak sądzę, będą dla ciebie skuteczne. Czy twoje pole identyfikacyjne to pole autoreferencji?jeśli tak, powinno działać rozwiązanie wybierania losowych identyfikatorów. –

2

Można wygenerować jakąś pseudo losową wartość w oparciu o identyfikatory i bieżącego czasu:

ORDER BY 37*(UNIX_TIMESTAMP()^system.id) & 0xffff 

będzie mieszać ukąszenia od id, a następnie zajmie tylko najniższą 16.

+0

Wydaje się być równie powolny ... – Brandon

2

Istnieją dwa główne powody powolności:

  • SQL musi najpierw wystawić liczbę losową dla każdego z wierszy
  • Wiersze musi być uporządkowane na podstawie tego numeru, aby wybrać 200 najlepszych z nich

Jest trik, aby pomóc w tej sytuacji, to wymaga trochę Praca przygotowawcza i sposób jej wdrożenia (i jej względne zainteresowanie) zależą od rzeczywistego przypadku użycia.

==> Przedstaw dodatkową kolumnę o wartości „random kategorii” filtrować out większość wierszy

Chodzi o to, aby mieć całkowitą kolumnę z wartościami z wartościami przydzielonych losowo, raz po raz, z prep wartość między 0 a 9 (lub 1 i 25 ... dowolne). Ta kolumna musi następnie zostać dodana do indeksu użytego w zapytaniu. Ostatecznie, modyfikując zapytanie w celu włączenia filtru do tej kolumny = konkretnej wartości (np. 3), liczba wierszy, które SQL musi obsłużyć, jest następnie zmniejszana o 10 (lub 25, w zależności od liczby odrębnych wartości, które mamy w Termin „kategoria random”.

Zakładając tę ​​nową kolumnę nazywa RandPreFilter moglibyśmy wprowadzono indeks jak

CREATE [UNIQUE ?] INDEX 
ON system (id, RandPreFilter) 

i zmienić zapytanie następująco

SELECT system.id 
    , system.username 
    , system.password 
    , system.followed 
    , system.isvalid 
    , follows.userid 
    , follows.systemid 
FROM system 
LEFT JOIN follows ON system.id = follows.systemid 
    AND follows.userid = 2 
WHERE system.followed=0 AND system.isvalid=1 
    AND follows.systemid IS NULL 

    AND RandPreFilter = 1 -- or other numbers, or possibly 
     -- FLOOR(1 + RAND() * 25) 
ORDER BY RAND() 
LIMIT 200 
5

powód kwerenda jest powolne jest to, że baza danych musi zachować reprezentację wszystkich wygenerowane wartości losowe i ich odpowiednie dane, zanim będzie możliwe zwrócenie nawet jednego wiersza z bazy danych.Możesz ograniczyć liczbę kandydujących wierszy do rozważenia najpierw, używając WHERE RAND() < x, gdzie wybierz x, aby być liczbą, która prawdopodobnie zwróci co najmniej tyle próbek, ile potrzebujesz. Aby otrzymać prawdziwą losową próbkę, musisz ponownie zamówić przez RAND lub wykonać próbkowanie na zwróconym zestawie danych.

Dzięki temu podejściu baza danych może przetworzyć zapytanie w trybie strumieniowym bez konieczności tworzenia dużej pośredniej reprezentacji wszystkich danych. Wadą jest to, że nigdy nie możesz być w 100% pewny, że uzyskasz wymaganą liczbę próbek, więc może będziesz musiał ponownie wykonać zapytanie, dopóki nie zrobisz, żyć z mniejszym zestawem próbek lub stopniowo dodawać próbki (unikając duplikatów), dopóki nie uzyskasz wymaganej liczby próbek.

Jeśli nie chcesz, aby zapytanie zwracało różne wyniki dla każdego połączenia, możesz również dodać kolumnę z losową kolumną z indeksem i połączyć ją z powyższą techniką. Umożliwi to uzyskanie dowolnej liczby próbek w uczciwy sposób, nawet jeśli dodasz lub usuniesz wiersze, ale to samo zapytanie na tych samych danych zwróci oczywiście ten sam zestaw wyników.

1

W zależności od tego, jakie dane mają być losowe, warto zamówić dane i dodać dodatkową "ostatnio używaną" kolumnę datetime i zaktualizować ją po skorzystaniu z danych. Następnie wybierz porządek w górę n rzędów przez ostatnie używane pole malejące.

Jeśli zawiniesz to w przygotowanej instrukcji, możesz wybrać jeden (pół) losowy wynik na raz, nie martwiąc się o logikę.

Można także podać każdemu wierszowi identyfikator sekwencyjny i wygenerować losowość w kodzie i wycofać tylko wymagane wiersze. Problem polega na tym, że pełny zestaw rekordów jest zwracany przed zamówieniem.

0

Może trochę późno, ale przynajmniej tu jest dodatkowym rozwiązaniem dla przyszłego uwagę:

SELECT minSystem.id, 
    minSystem.username, 
    minSystem.password, 
    minSystem.followed, 
    minSystem.isvalid, 
    randFollows.userid, 
    randFollows.systemid 
FROM 
(
    SELECT * 
    FROM system 
    WHERE system.followed = 0 AND system.isvalid = 1 
) as minSystem 
LEFT JOIN 
(
    SELECT * 
    FROM (
     SELECT * 
     FROM follows 
     WHERE follows.systemid IS NULL 
    ) as minFollows 
    WHERE rand() <= 200 * 1.5/(SELECT count(*) FROM follows WHERE systemid IS NULL) 
) as randFollows 
ON minSystem.id = randFollows.systemid 
LIMIT 200 

Najpierw dokonujemy wyboru na tabeli systemowej na celu zmniejszenie rozmiaru tabeli temp minSystem i minFollow. Następnie wybieramy losowe wiersze z tabeli minFollows poprzez obliczone prawdopodobieństwo. Do tej pory będziemy mieli dość losową tabelę randFollows do LEFT JOIN z minSystem. Wreszcie robimy LIMIT 200.

Jeśli korzystasz z MyISam, możesz po prostu pobrać rozmiar tabeli. Eliminuje to dodatkowe podzapytanie do obliczenia rozmiaru tabeli follows. Alternatywnie możesz również zakodować kodem, jeśli Twój rozmiar tabeli nie rośnie zbyt szybko (wymaga to jednak ręcznej konserwacji).

Dla dokładniejszego wyjaśnieniu, proszę kasy rozwiązanie ja Wysłany: MySQL: Alternatives to ORDER BY RAND()

Hope this helps (a przynajmniej mam nadzieję, że znajdziesz to ciekawe)!

Powiązane problemy