Zastosowanie ORDER BY RAND() generuje tabelę tymczasową. Zawsze. Administratorzy baz danych nie lubią tabel tymczasowych, bo powodują że zapytanie działa wolno. Niestety, osoby piszące aplikacje korzystające z bazy MySQL często są nieświadome tego, jak dużym problemem może być zastosowanie tego typu sortowania wyników.
Użycie ORDER BY RAND() wymaga od bazy danych utworzenia tabeli tymczasowej z wynikiem zapytania, przydzielenia wszystkim rekordom losowych współczynników, po których są one następnie sortowane. Jeśli tabela tymczasowa miałaby mieć wielkość do kilkuset rekordów, nie stanowi to jeszcze problemu. Problem pojawia się natomiast w momencie, gdy ilość rekordów przekracza kilka tysięcy i pogłębia się wraz ze wzrostem wielkości tabeli.
Jak można zastąpić zastosowanie ORDER BY RAND()? Zależy to od tego, o jakie konkretnie zapytanie chodzi. Jeśli chcemy wyciągnąć pojedynczy, losowy rekord, to względnie optymalnym rozwiązaniem, ale wymagającym istnienia w tabeli jakiejś kolumny z unikalnymi identyfikatorami rekordów będzie:
Z przedziału od 0 do wyniku powyższego zapytania generujemy losową liczbę (X), a następnie próbujemy znaleźć jakiś rekord o id do niej zbliżonym.
Jeśli potrzeba nam wylosować więcej rekordów, to sprawa trochę się komplikuje. Aby zastąpić zapytanie typu:
przy założeniu, że w tabeli mamy jakąś kolumnę z identyfikatorem, idealnie gdyby była autoincrementowana od 1 w górę (jeśli nie mamy, to trzeba zrobić tak, żeby była – dodać kolumnę i odpowiednio wypełnić), należałoby zrobić coś takiego:
Z przedziału 1 – wynik powyższego zapytania generujemy następnie tyle liczb losowych, ile potrzebujemy. Jeśli tabela jest pofragmentowana (zawartość kolumny `kol_id` jest nieciągła ze względu na usuwanie niektórych rekordów), możemy wygenerować takich liczb 10 – 100 razy więcej. Wygenerowane liczby wykorzystujemy jako argumenty do listy IN() w następującym zapytaniu:
gdzie X jest liczbą potrzebnych nam rekordów.
Inną opcją jest stworzenie i wypełnienie tabeli zawierającej losowe wartości z zakresu 0 – MAX(kol_id) w ilościach dużych (kilkaset tysięcy rekordów):
Jeśli konieczne jest wybranie 10 losowych rekordów, to bierzemy z takiej tabeli pierwsze dziesięć, zapisujemy w aplikacji na którym skończyliśmy i przy następnej okazji zaczynamy od jedenastej z kolei losowej wartości. Taką tabelę można co jakiś czas generować od nowa, można też po prostu ją zapętlić – gdy doszliśmy do ostatniego rekordu kolejne wartości pobieramy od pierwszego.
Rozwiązania te wymagają więcej pracy niż dodanie do SELECT’a ORDER BY RAND(), zgoda. W zamian za tą dodatkową pracę umożliwiają aplikacji skalowanie się poza kilkaset rekordów w bazie. Tworząc aplikację należy pamiętać o tym, że te same zapytania i ta sama struktura bazy danych może się kompletnie inaczej zachowywać w przypadku bazy o wielkości kilku megabajtów, a inaczej w przypadku bazy wielkości kilkuset megabajtów. Jednym z powodów problemów z wydajnością, z jakimi się spotykam, jest to, że nikt nie testował aplikacji na większym zestawie danych. Instalujemy forum, dodajemy kilkanaście testowych postów – działa miło i szybko. Problem pojawia się w momencie, gdy forum stanie się popularne i liczba postów sięgnie np. kilka tysięcy. Okazuje się, że trzeba stawiać serwer fizyczny za sporo kilozłotych rocznie, żeby pociągnął to jedno forum. A wystarczyłoby aby twórca przetestował swoją aplikację na większej bazie danych i wprowadził odpowiednie poprawki.
Komentarze