Walian
22.03.2010, 01:05:19
Zna ktoś jakiś szybki sposób na wybranie 12 losowych rekordów z tabeli, która może mieć nawet kilka milionów wierszy? Do tego biorąc pod uwagę, że będzie bardzo często odczytywana.
Najpierw próbowałem tego:
http://blog.desmart.com/2008/03/12/sposob-...-tablicy-mysql/ale losuje mi 12 rekordów, tyle, że następujących po sobie jeden po drugim, czyli tak naprawdę losuje miejsce startowe, skąd ma pobrać kolejne rekordy. Nie umiem tego przerobić tak by wybrało 12 zupełnie przypadkowych rekordów.
Pomyślałem więc, że wylosuję w PHP ok. 50 liczb, i używając ich zadam zapytanie:
SELECT * FROM tabela WHERE pole_id IN (liczba1,liczba2...)
Ale to mi nie daje gwarancji, że wybierze dokładnie 12 rekordów, bo przecież niektóre o podanych ID mogą nie istnieć. W sumie mogę tak wybierać rekordy aż uzyskam dokładnie 12 (bo to mała liczba) ale może zna ktoś jakiś lepszy sposób?
luki100011
22.04.2010, 11:59:49
Kod
SELECT * FROM tabela ORDER BY RAND() LIMIT 12
nospor
22.04.2010, 12:04:01
@luki100011 zanim udzielisz jakze pomocnej odpowiedzi, przeczytaj najpierw co autor napisał...
W linku co podal metoda ta też jest podana. Jest też podane tam rownież dlaczego nalezy jej nie uzywac. Przeczytaj, wiedza ta moze ci sie przyda w przyszlosci
phpion
22.04.2010, 12:07:52
Może więc w PHP losuj 12 + X liczb, gdzie X to odpowiedni "zapas" jeśli danego artykuły by nie było. Następnie wybierasz rekordy z warunkiem IN (wylosowane_liczby) LIMIT 12. Jeśli dobierzesz odpowiednią wartość X (odpowiednio duża jeśli jest duże prawdopodobieństwo trafienia na "dziurę") to będziesz miał naprawdę pecha jeśli zapytanie nie zwróci Ci faktycznie 12 rekordów.
Mchl
22.04.2010, 12:56:31
nospor
22.04.2010, 13:02:29
@Mchl Twoje rozwiązanie również pobiera 1 rekord.
phpion
22.04.2010, 13:04:22
@Mchl:
Zgadza się, jest to dość sprytne rozwiązanie, ale w tym przypadku chyba nie do końca przypadnie autorowi do gustu. Jego minusem będzie to, że wybierze rekordy po kolei od zadanego (wylosowanego) punktu startowego.
@nospor:
Można przecież nadać ograniczenie, że liczba rekordów = rzeczywista liczba rekordów - limit i wtedy można pobrać X "losowych" rekordów.
Mchl
22.04.2010, 13:06:26
Jak zostosuje je dosłownie w takiej postaci jak przedstawiona to na pewno.
Ale kto mu broni wylosować 12 offsetów i złożyć przez UNION zapytanie 12 SELECTów?
phpion
22.04.2010, 13:08:01
No tak, ale dochodzi tutaj kwestia liczby wykonanych zapytań oraz możliwość powstawania powtórzeń w wylosowanych rekordach.
nospor
22.04.2010, 13:08:32
Cytat
Ale kto mu broni wylosować 12 offsetów i złożyć przez UNION zapytanie 12 SELECTów?
Ba.... nikt mu nie zabroni. Ale to mogl zrobic juz na samym początku przy rozwiązaniu ktore sam podał. A weź pobierz 12 losowych rekordów bez union i bez 12 zapytan....
Mchl
22.04.2010, 13:13:57
Kod
SELECT COUNT(*) FROM test.t_option;
> 2000000
Kod
SELECT SQL_NO_CACHE * FROM test.t_option ORDER BY RAND() LIMIT 1;
2.1342s
Kod
SELECT SQL_NO_CACHE * FROM test.t_option LIMIT 1 OFFSET 653143;
0.3164s
nospor
22.04.2010, 13:15:47
No i czemu te zestawienie ma sluzyc? Przeciez to oczywiste. Ba... nawet jest podane w pierwszym linku w pierwszym poscie. Jak to ma sie do 12 losowych rekordów bez UNION i bez 12 zapytan?
Mchl
22.04.2010, 13:19:08
Ale dlaczego bez UNION? Skąd taki wymóg?
nospor
22.04.2010, 13:21:26
oj... zmieniasz te fronty.. tu mi jakies wyniki, tu jakies zapytania i za kazdym razem unikasz odpowiedzi na pytanie

Cytat
Ale dlaczego bez UNION? Skąd taki wymóg?
zaden wymog. Wyjasniam ci jedynie ze UNION to on juz mogl uzyc na samym poczatku przy zapytaniu co podal. Zapytal sie jednak jak mozna to zrobic inaczej, a ty w odpowiedzi podales swoje zapytanie, inne, które tez pobiera jeden rekord.
phpion
22.04.2010, 13:21:48
Swoją drogą: czy przypadkiem rozwiązanie z IN (1, 2, 3) nie będzie przypadkiem szybsze? Przyznam, że wynik dla drugiego zapytania (0.3164s) jakoś szczególnie mnie nie powalił swą szybkością. Czy mógłbyś wykonać zapytanie z kilkoma identyfikatorami w IN?
nospor
22.04.2010, 13:22:45
@phpion ale przeciez dla IN musisz podac np. konkretne ID. A o dziurach nie slyszal?
phpion
22.04.2010, 13:23:31
Cytat(nospor @ 22.04.2010, 14:22:45 )

@phpion ale przeciez dla IN musisz podac np. konkretne ID. A o dziurach nie slyszal?

Cytat(phpion @ 22.04.2010, 13:07:52 )

Może więc w PHP losuj 12 + X liczb, gdzie X to odpowiedni "zapas" jeśli danego artykuły by nie było. Następnie wybierasz rekordy z warunkiem IN (wylosowane_liczby) LIMIT 12. Jeśli dobierzesz odpowiednią wartość X (odpowiednio duża jeśli jest duże prawdopodobieństwo trafienia na "dziurę") to będziesz miał naprawdę pecha jeśli zapytanie nie zwróci Ci faktycznie 12 rekordów.
nospor
22.04.2010, 13:23:47
ps: tak, czytalem twoje poprzednie posty o prawdopodobienstwu - nie podoba mi sie ta teoria
phpion
22.04.2010, 13:26:03
No mi generalnie również nie do końca bo zawsze może się zdarzyć przypadek, gdy zapytanie zwróci 0 rekordów (wylosujemy same "dziury"). Jednak wybieranie rekordów po PK powinno być zdecydowanie szybsze niż wycinanie danego fragmentu ze zbioru - stąd moja prośba do Mchl o wykonanie zapytania i podanie wyniku.
Mchl
22.04.2010, 13:27:16
Wszystkie metody opierające się na losowaniu wartości klucza głównego zakłądają, że klucz główny jest ciągły.
Jeśli nie jest, to na przykład zapytanie podane w linku w pierwszym poście, raz na jakiś czas zwróci 0 wierszy. Czy to dopuszczalne? Pewnie jak się ma tego świadomość i dobrze obsłuży to tak.
Jeżeli wywołanie pierwszego zapytania tyle razy żeby uzyskać dokładnie 12 wierszy będzie w 99,99% szybsze niż wywołanie 12 razy rozwiązania podanego przeze mnie, to chyba wiadomo jaki jest wybór.
@phpion: niestety akurat ta tabela nie ma klucza głównego auto_increment, więc trudno byłoby zrobić porównanie. Niewątpliwie jednak, wybieranie po PK będzie szybsze. Pytanie tylko, jak często losując wartość PK trafiamy na wartość rzeczywiście istniejącą.
melkorm
22.04.2010, 13:29:48
Może po prostu wykorzystując metodę phpion'a dodać do warunku OR 1=1 i przy LIMIT 12 to by wypełniło dziury, ale faktem jest że rekordy wypełniające dziury będą w większości przypadkach takie same.
nospor
22.04.2010, 13:33:29
Cytat
dodać do warunku OR 1=1
wydaje mi się, ze mysql wowczas potraktuje cale wyrazenie jako prawda i poleci pokolei bez patrzenia na IN. Ale mogę sie mylic - mozna sprawdzic.
edit: sprawdzilem - jest tak jak powiedzialem
Mchl
22.04.2010, 13:34:04
Nie ma co sprawdzać. Dokładnie tak będzie. Jakikolwiek dodatkowy warunek w alternatywie z wyrażeniem, któe zawsze ma wartość TRUE będzie wyrzucony jeszcze na poziomie optymalizera.
nospor
22.04.2010, 13:36:07
mozna połaczyc dwie metody:
IN
UNION
limit 12 z losowym ofsetem
I jesli IN zawiedzie (nie zwroci 12 losowych rekordów) to dopelni się je kolejnymi rekordami z losowego offsetu
Mchl
22.04.2010, 13:39:37
Jeżeli taka 'losowość' wystarcza autorowi pytania, to można. Mnie się to nie bardzo podoba

W dzisiejszym losowaniu multilotka wylosowano 4,7,2,6,43,21,42,10,11,12,13,14...
phpion
22.04.2010, 13:41:03
Sprawdziłem u siebie na bazie.
Rekordów w tabeli: blisko 15mln
Zapytanie z UNIONami: 34.662 ms
Zapytanie z IN: 1.371 ms
Każde zapytanie uruchamiałem 5 razy i dopiero przy piątym uruchomieniu spisywałem wyniki (czasy się ustabilizowały). Co prawda baza to akurat PostgreSQL no ale jakieś odniesienie można mieć. Pozostają jednak problemy z "dziurami".
nospor
22.04.2010, 13:46:14
Cytat
Jeżeli taka 'losowość' wystarcza autorowi pytania, to można. Mnie się to nie bardzo podoba
Ja tylko podalem usprawnienie do metody phpiona. nie pisalem ze jest idealne
Mchl
22.04.2010, 13:49:07
Dlatego wybór metody trzeba uzależnić od rzeczywistych danych. Może w tej tabeli dane są tylko dodawane i nigdy nie usuwane, albo usuniętych wartość PK jest nie więcej niż 10%.Wtedy można śmiało wybierać wartości klucza głównego, bo średnio powinno wystarczyć ok 13 losowań, żeby trafić na 12 istniejących.
Z drugiej strony, jeżeli z tabeli dane są usuwane "często i gęsto", wtedy może być lepiej polecieć z offsetem.
luki100011
22.04.2010, 18:14:24
Cytat(nospor @ 22.04.2010, 13:04:01 )

@luki100011 zanim udzielisz jakze pomocnej odpowiedzi, przeczytaj najpierw co autor napisał...
W linku co podal metoda ta też jest podana. Jest też podane tam rownież dlaczego nalezy jej nie uzywac. Przeczytaj, wiedza ta moze ci sie przyda w przyszlosci
czyli zgadzasz się z nim że to zapytanie wylosuje po kolie 12 rekordów a nie losowe
nospor
22.04.2010, 19:43:35
Cytat
czyli zgadzasz się z nim że to zapytanie wylosuje po kolie 12 rekordów a nie losowe
mowisz o order by rand() ?
Nie, to zapytanie zwroci 12 losowych rekordów. Ale on o tym wie, ma to napisane w linku co sam podal. Nie nalezy tej metody jednak uzywac bo jest cholernie nie optymalna i przy duzej ilosci rekordów, jaka tu ma miejsce, szlag wszystko trafi. To mialem na mysli piszac: ". Przeczytaj, wiedza ta moze ci sie przyda w przyszlosci"
luki100011
22.04.2010, 21:27:26
On napisał że zwraca mu po kolei 12 rekordów
Cytat
ale losuje mi 12 rekordów, tyle, że następujących po sobie jeden po drugim, czyli tak naprawdę losuje miejsce startowe, skąd ma pobrać kolejne rekordy. Nie umiem tego przerobić tak by wybrało 12 zupełnie przypadkowych rekordów
nospor
22.04.2010, 21:29:20
no tak. zapytanie ktore podal, te ktore mialo LIMIT 1, po wstawieniu tam LIMIT 12, zwraca 12 rekordów nastepujacych po sobie.
phpion
23.04.2010, 06:54:02
@luki100011:
Słowo-klucz zawarte jest w temacie; jest nim szybko, a Twoje zapytanie nie spełnia tego warunku.
luki100011
23.04.2010, 09:16:20
Trudno będzie znaleźć coś innego przecież nie pobierzesz całej tabeli (np kolumny id) do tablic aby w php wylosować 12 jej elementów.
Jak nie musi cały czas być to inne 12 elementów np 12 innych co godzinę no to cache i będzie szybko
nospor
23.04.2010, 09:20:50
zrob sobie kilku milionową tabele i zrob na niej order by rand()
To juz lepiej bawic sie w 12 union czy w 12 zapytan niż w to, co ty zaproponowales a co bylo wiadome juz od samego początku bo bylo w linku co podal autor, co podkreslam ci jeszcze raz
Mchl
23.04.2010, 11:51:05
Cytat(luki100011 @ 23.04.2010, 10:16:20 )

Trudno będzie znaleźć coś innego
Jak przejrzysz ten wątek, to znajdziesz conajmniej trzy szybsze sposoby.
Pilsener
23.04.2010, 19:25:03
Ja bym proponował:
- użycie IN z odpowiednim zapasem losowych ID, jeśli się zdarzy, że będzie mniej niż 12 rekordów to trudno, niech się wykona jeszcze raz (lepsze pewnie nawet 1,5 zapytania niż 12 unionów)
- zawężenie zakresu wybierania rekordów poprzez between, gdzie parametry dla between są losowane - ale nie wiem jak z wydajnością tego, czy to coś przyśpieszy
A osobiście by zrobił:
- dodatkową tabelę, która przechowuje określony % identyfikatorów głównej tabeli a rekordy w niej są odświeżane co jakiś czas, czas ten oraz % rekordów dobieramy wg potrzeb i uznania
Mchl
23.04.2010, 19:45:48
Cytat(Pilsener @ 23.04.2010, 20:25:03 )

- zawężenie zakresu wybierania rekordów poprzez between, gdzie parametry dla between są losowane - ale nie wiem jak z wydajnością tego, czy to coś przyśpieszy
Wątpliwe. Trzebaby sprawdzić OC.
Cytat
A osobiście by zrobił:
- dodatkową tabelę, która przechowuje określony % identyfikatorów głównej tabeli a rekordy w niej są odświeżane co jakiś czas, czas ten oraz % rekordów dobieramy wg potrzeb i uznania
W obu przypadkach ogranicza się 'losowość' wyboru.
maly_swd
24.04.2010, 10:19:28
a takie cos?
http://jan.kneschke.de/projects/mysql/order-by-rand/ nie sprawdzalem ale na oko jakies inne podejscie niz tu proponujecie;)
Mchl
24.04.2010, 13:39:13
Dodatkowa tabela w której trzyma się same wartości klucza głównego? Oczywiście można... Nie podobają mi się tylko te triggery, zwłaszcza AFTER DELETE: kasujesz jeden wiersz z początku tabeli, a trigger uaktualnia miliony wierszy w tabeli skojarzonej...
Kto jest chętny sprawdzić jak wydajność ma się do tego co już omówiliśmy? Podejrzewam że w okolicach tego co dla WHERE ID IN (...). Natomiast przy kasowaniu wierszy może być nieciekawie.
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę
kliknij tutaj.