MySQL szybko wybiera 10 losowych wierszy z 600 000 wierszy

463

Jak najlepiej napisać zapytanie, które wybiera losowo 10 wierszy z łącznej liczby 600 000?

Franciszek
źródło
15
Oto 8 technik ; być może jeden będzie działał dobrze w twoim przypadku.
Rick James,

Odpowiedzi:

386

Świetny post obsługujący kilka przypadków, od prostych, przez luki, po niejednolitą z lukami.

http://jan.kneschke.de/projects/mysql/order-by-rand/

W najbardziej ogólnym przypadku oto jak to zrobić:

SELECT name
  FROM random AS r1 JOIN
       (SELECT CEIL(RAND() *
                     (SELECT MAX(id)
                        FROM random)) AS id)
        AS r2
 WHERE r1.id >= r2.id
 ORDER BY r1.id ASC
 LIMIT 1

Zakłada to, że rozkład identyfikatorów jest równy i że na liście identyfikatorów mogą występować przerwy. Zobacz artykuł, aby uzyskać bardziej zaawansowane przykłady

Riedsio
źródło
52
Tak, jeśli masz potencjalnie duże luki w identyfikatorach, szansa na losowe wybranie najniższego identyfikatora jest znacznie mniejsza niż twoich wysokich identyfikatorów. W rzeczywistości szansa, że ​​pierwszy identyfikator po wybraniu największej luki jest w rzeczywistości najwyższy. Dlatego z definicji nie jest to przypadek.
lukeocodes
6
Jak uzyskać 10 różnych losowych wierszy? Czy musisz ustawić limit na 10, a następnie iterować 10 razy mysqli_fetch_assoc($result)? A może te 10 wyników niekoniecznie da się rozróżnić?
Adam
12
Moim zdaniem Random wymaga jednakowej szansy na jakikolwiek wynik. ;)
lukeocodes
4
Pełny artykuł dotyczy problemów takich jak nierówne rozkłady i powtarzające się wyniki.
Bradd Szonye
1
w szczególności, jeśli masz luki na początku swoich identyfikatorów, pierwszy zostanie wybrany (min / maks-min) danego czasu. W takim przypadku prostą modyfikacją jest MAX () - MIN () * RAND + MIN (), co nie jest zbyt wolne.
Code Abominator
342
SELECT column FROM table
ORDER BY RAND()
LIMIT 10

Nie jest to skuteczne rozwiązanie, ale działa

Preetam Purbia
źródło
139
ORDER BY RAND()jest stosunkowo wolny
Mateusz Charytoniuk
7
Mateusz - dowód pls, SELECT words, transcription, translation, sound FROM vocabulary WHERE menu_id=$menuId ORDER BY RAND() LIMIT 10zajmuje 0,0010, bez LIMITU 10 zabrał 0,0012 (w tej tabeli 3500 słów).
Arthur Kushman
26
@zeusakm 3500 słów to niewiele; problem polega na tym, że eksploduje po pewnym punkcie, ponieważ MySQL musi właściwie posortować WSZYSTKIE rekordy po przeczytaniu każdego z nich; gdy ta operacja uderzy w twardy dysk, poczujesz różnicę.
Ja͢ck
16
Nie chcę się powtarzać, ale znowu, to pełny skan tabeli. Na dużym stole zajmuje to dużo czasu i pamięci i może powodować tworzenie i działanie tymczasowego stołu na dysku, co jest bardzo wolne.
mat
10
Kiedy przeprowadzałem wywiad z Facebookiem w 2010 r., Zapytali mnie, jak wybrać losowy zapis z ogromnego pliku o nieznanej wielkości w jednym czytaniu. Gdy wpadniesz na pomysł, łatwo go uogólnić, wybierając wiele rekordów. Tak, sortowanie całego pliku jest absurdalne. Jednocześnie jest bardzo przydatny. Właśnie zastosowałem to podejście, aby wybrać 10 losowych wierszy ze stołu z ponad 1 000 000 wierszy. Jasne, musiałem trochę poczekać; ale chciałem tylko dowiedzieć się, jak wyglądają typowe wiersze w tej tabeli ...
osa
27

Proste zapytanie, które ma doskonałą wydajność i działa z lukami :

SELECT * FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY RAND() LIMIT 10) as t2 ON t1.id=t2.id

To zapytanie na stole 200K zajmuje 0.08s i normalnej wersji (select * from tbl ORDER BY RAND () LIMIT 10) wykonuje 0.35s na moim komputerze.

Jest to szybkie, ponieważ w fazie sortowania używana jest tylko indeksowana kolumna identyfikatora. Możesz zobaczyć to zachowanie w wyjaśnieniu:

WYBIERZ * Z tbl ORDER BY RAND () LIMIT 10: Proste wyjaśnienie

WYBIERZ * Z tbl JAK DOŁĄCZ DO t1 (WYBIERZ identyfikator z tbl ORDER BY RAND () LIMIT 10) jako t2 ON t1.id = t2.id wprowadź opis zdjęcia tutaj

Wersja ważona : https://stackoverflow.com/a/41577458/893432

Ali
źródło
1
Przepraszam, testowałem! niska wydajność na 600 000 rekordach.
Dylan B
@DylanB Zaktualizowałem odpowiedź testem.
Ali
17

Dostaję szybkie zapytania (około 0,5 sekundy) z wolnym procesorem , wybierając 10 losowych wierszy w 400 000 rejestrów bazy danych MySQL niebuforowanej wielkości 2 GB. Zobacz mój kod: Szybki wybór losowych wierszy w MySQL

<?php
$time= microtime_float();

$sql='SELECT COUNT(*) FROM pages';
$rquery= BD_Ejecutar($sql);
list($num_records)=mysql_fetch_row($rquery);
mysql_free_result($rquery);

$sql="SELECT id FROM pages WHERE RAND()*$num_records<20
   ORDER BY RAND() LIMIT 0,10";
$rquery= BD_Ejecutar($sql);
while(list($id)=mysql_fetch_row($rquery)){
    if($id_in) $id_in.=",$id";
    else $id_in="$id";
}
mysql_free_result($rquery);

$sql="SELECT id,url FROM pages WHERE id IN($id_in)";
$rquery= BD_Ejecutar($sql);
while(list($id,$url)=mysql_fetch_row($rquery)){
    logger("$id, $url",1);
}
mysql_free_result($rquery);

$time= microtime_float()-$time;

logger("num_records=$num_records",1);
logger("$id_in",1);
logger("Time elapsed: <b>$time segundos</b>",1);
?>
snippetsofcode
źródło
11
Biorąc pod uwagę moją ponad 14 milionów tabel rekordów, jest to tak powolne jakORDER BY RAND()
Fabrizio
5
@snippetsofcode W twoim przypadku - 400 tys. wierszy możesz użyć prostego „ZAMÓWIENIA według rand ()”. Twoja sztuczka z 3 zapytaniami jest bezużyteczna. Można przerobić go jak "SELECT id, URL ze stron WHERE id IN (SELECT id FROM stron ORDER BY rand () LIMIT 10)"
Roman Podlinov
4
Twoja technika nadal wykonuje skanowanie tabeli. Użyj, FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler%';aby to zobaczyć.
Rick James
4
Spróbuj także uruchomić to zapytanie na stronie 200 req / s. Współbieżność cię zabije.
Marki555
@RomanPodlinov ma tę przewagę nad zwykłym ORDER BY RAND(), że sortuje tylko identyfikatory (nie pełne wiersze), więc tabela temp jest mniejsza, ale nadal musi sortować wszystkie.
Marki555,
16

Jest to bardzo proste i jedno wierszowe zapytanie.

SELECT * FROM Table_Name ORDER BY RAND() LIMIT 0,10;
Muhammad Azeem
źródło
20
FYI, order by rand()jest bardzo powolny, jeśli stół jest duży
evilReiko
6
Czasami SLOW jest akceptowany, jeśli chcę, aby był PROSTY
Indeksowanie powinno być stosowane na stole, jeśli jest duży.
Muhammad Azeem,
1
Indeksowanie tutaj nie pomoże. Indeksy są pomocne w przypadku bardzo konkretnych rzeczy, a to zapytanie nie jest jednym z nich.
Andrew
13

Z książki:

Wybierz losowy wiersz za pomocą przesunięcia

Jeszcze inną techniką, która pozwala uniknąć problemów znalezionych w poprzednich alternatywach, jest zliczanie wierszy w zestawie danych i zwracanie losowej liczby między 0 a liczbą. Następnie użyj tego numeru jako przesunięcia przy wyszukiwaniu zestawu danych

<?php
$rand = "SELECT ROUND(RAND() * (SELECT COUNT(*) FROM Bugs))";
$offset = $pdo->query($rand)->fetch(PDO::FETCH_ASSOC);
$sql = "SELECT * FROM Bugs LIMIT 1 OFFSET :offset";
$stmt = $pdo->prepare($sql);
$stmt->execute( $offset );
$rand_bug = $stmt->fetch();

Skorzystaj z tego rozwiązania, gdy nie możesz założyć ciągłych wartości klucza i musisz upewnić się, że każdy wiersz ma równą szansę na wybranie.

zloctb
źródło
1
dla bardzo dużych tabel SELECT count(*)staje się wolny.
Hans Z
7

Jak wybrać losowe wiersze z tabeli:

Stąd: wybierz losowe wiersze w MySQL

Szybkim ulepszeniem w stosunku do „skanowania tabeli” jest użycie indeksu do pobrania losowych identyfikatorów.

SELECT *
FROM random, (
        SELECT id AS sid
        FROM random
        ORDER BY RAND( )
        LIMIT 10
    ) tmp
WHERE random.id = tmp.sid;
użytkownik1931858
źródło
1
To pomaga niektórym MyISAM, ale nie InnoDB (zakładając, że id jest klastrowany PRIMARY KEY).
Rick James
7

Cóż, jeśli nie masz żadnych przerw w klawiszach, a wszystkie są numeryczne, możesz obliczyć losowe liczby i wybrać te linie. ale prawdopodobnie tak nie będzie.

Tak więc jednym rozwiązaniem byłoby:

SELECT * FROM table WHERE key >= FLOOR(RAND()*MAX(id)) LIMIT 1

co w zasadzie zapewni, że otrzymasz liczbę losową w zakresie swoich klawiszy, a następnie wybierzesz następny najlepszy, który jest większy. musisz to zrobić 10 razy.

jednak NIE jest to tak naprawdę losowe, ponieważ twoje klucze najprawdopodobniej nie zostaną rozłożone równomiernie.

To naprawdę duży problem i niełatwy do rozwiązania, spełniający wszystkie wymagania, rand () MySQL-a jest najlepszym, co możesz uzyskać, jeśli naprawdę chcesz 10 losowych wierszy.

Istnieje jednak inne rozwiązanie, które jest szybkie, ale ma również kompromis, jeśli chodzi o przypadkowość, ale może ci bardziej odpowiadać. Przeczytaj o tym tutaj: jak mogę zoptymalizować funkcję ORDER BY RAND () w MySQL?

Pytanie brzmi, jak losowo potrzebujesz.

Czy możesz wyjaśnić coś więcej, abym mógł dać ci dobre rozwiązanie.

Na przykład firma, z którą współpracowałem, miała rozwiązanie, w którym bardzo szybko potrzebowała absolutnej przypadkowości. Skończyło się na wstępnym zapełnieniu bazy danych losowymi wartościami, które zostały wybrane malejąco i ponownie ustawione na różne wartości losowe.

Jeśli prawie nigdy nie aktualizujesz, możesz również wypełnić rosnący identyfikator, aby nie mieć żadnych luk i po prostu obliczyć losowe klucze przed wybraniem ... To zależy od przypadku użycia!

Surrican
źródło
Cześć Joe. W tym konkretnym przypadku kluczom nie powinno zabraknąć luk, ale z czasem może się to zmienić. I podczas gdy twoja odpowiedź działa, wygeneruje losowe 10 wierszy (pod warunkiem, że piszę limit 10), które są kolejne i chciałem, żeby tak rzec, więcej losowości. :) Dziękuję Ci.
Francisc
Jeśli potrzebujesz 10, użyj jakiegoś połączenia, aby wygenerować 10 unikalnych wierszy.
johno
to, co powiedziałem. musisz to wykonać 10 razy. łączenie go wition union jest jednym ze sposobów umieszczenia go w jednym zapytaniu. zobacz moje uzupełnienie 2 minuty temu.
The Surrican
1
@TheSurrican, To rozwiązanie wygląda fajnie, ale jest bardzo wadliwe . Spróbuj wstawić tylko jedno bardzo duże, Ida wszystkie losowe zapytania zwrócą ci to Id.
Pacerier,
1
FLOOR(RAND()*MAX(id))jest tendencyjny do zwracania większych identyfikatorów.
Rick James
3

Potrzebowałem zapytania, aby zwrócić dużą liczbę losowych wierszy z dość dużej tabeli. Właśnie to wymyśliłem. Najpierw uzyskaj maksymalny identyfikator rekordu:

SELECT MAX(id) FROM table_name;

Następnie zamień tę wartość na:

SELECT * FROM table_name WHERE id > FLOOR(RAND() * max) LIMIT n;

Gdzie max to maksymalny identyfikator rekordu w tabeli, a n to liczba wierszy, które chcesz w zestawie wyników. Zakłada się, że w identyfikatorze nie ma żadnych luk, chociaż wątpię, by to wpłynęło na wynik, gdyby były (choć nie próbowałem). Stworzyłem również tę procedurę składowaną, aby była bardziej ogólna; przekazać nazwę tabeli i liczbę wierszy do zwrócenia. Korzystam z MySQL 5.5.38 w systemie Windows 2008, 32 GB, podwójnym 3GHz E5450, a na stole z 17 691 264 rzędów jest dość spójny przy ~ 0,03 s / ~ 11 s, aby zwrócić 1 000 000 wierszy. (czasy pochodzą z MySQL Workbench 6.1; możesz również użyć CEIL zamiast FLOOR w 2. instrukcji select, w zależności od twoich preferencji)

DELIMITER $$

USE [schema name] $$

DROP PROCEDURE IF EXISTS `random_rows` $$

CREATE PROCEDURE `random_rows`(IN tab_name VARCHAR(64), IN num_rows INT)
BEGIN

SET @t = CONCAT('SET @max=(SELECT MAX(id) FROM ',tab_name,')');
PREPARE stmt FROM @t;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @t = CONCAT(
    'SELECT * FROM ',
    tab_name,
    ' WHERE id>FLOOR(RAND()*@max) LIMIT ',
    num_rows);

PREPARE stmt FROM @t;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
$$

następnie

CALL [schema name].random_rows([table name], n);
użytkownik2406626
źródło
3

Poprawiłem odpowiedź @Riedsio. Jest to najbardziej wydajne zapytanie, jakie mogę znaleźć w dużej, równomiernie rozłożonej tabeli z przerwami (testowane na uzyskaniu 1000 losowych wierszy z tabeli, która ma> 2,6B wierszy).

(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max := (SELECT MAX(id) FROM table)) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1)

Pozwól mi rozpakować, co się dzieje.

  1. @max := (SELECT MAX(id) FROM table)
    • Obliczam i zapisuję maks. W przypadku bardzo dużych tabel istnieje niewielkie obciążenie związane z obliczaniem za MAX(id)każdym razem, gdy potrzebujesz wiersza
  2. SELECT FLOOR(rand() * @max) + 1 as rand)
    • Pobiera losowy identyfikator
  3. SELECT id FROM table INNER JOIN (...) on id > rand LIMIT 1
    • To wypełnia luki. Zasadniczo, jeśli losowo wybierzesz liczbę w lukach, po prostu wybierze następny identyfikator. Zakładając, że luki są równomiernie rozmieszczone, nie powinno to stanowić problemu.

Wykonanie unii pomaga dopasować wszystko do 1 zapytania, dzięki czemu można uniknąć wykonywania wielu zapytań. Pozwala także zaoszczędzić na obliczeniachMAX(id) . W zależności od aplikacji może to mieć duże lub bardzo małe znaczenie.

Zauważ, że to pobiera tylko identyfikatory i porządkuje je w losowej kolejności. Jeśli chcesz zrobić coś bardziej zaawansowanego, zalecamy zrobienie tego:

SELECT t.id, t.name -- etc, etc
FROM table t
INNER JOIN (
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max := (SELECT MAX(id) FROM table)) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1)
) x ON x.id = t.id
ORDER BY t.id
Hans Z
źródło
Potrzebuję 30 losowych rekordów, więc powinienem przejść LIMIT 1do LIMIT 30zapytania wszędzie
Hassaan
@Hassaan nie powinieneś, że zmiana LIMIT 1na LIMIT 30dostanie 30 rekordów z rzędu od losowego punktu w tabeli. Zamiast tego powinieneś mieć 30 kopii (SELECT id FROM ....części na środku.
Hans Z
Próbowałem, ale nie wydaje się bardziej skuteczny niż Riedsioodpowiedź. Próbowałem z 500 trafieniami na sekundę na stronę przy użyciu PHP 7.0.22 i MariaDB na centos 7, z Riedsioodpowiedzią dostałem ponad 500 pozytywnych odpowiedzi, a następnie twoją odpowiedź.
Hassaan,
1
@Hassaan odpowiedź Riedsio daje 1 wiersz, ten daje ci n wierszy, a także zmniejsza obciążenie we / wy na potrzeby zapytań. Być może będziesz w stanie uzyskać wiersze szybciej, ale przy większym obciążeniu systemu.
Hans Z
3

Użyłem tego http://jan.kneschke.de/projects/mysql/order-by-rand/ opublikowanego przez Riedsio (użyłem przypadku procedury składowanej, która zwraca jedną lub więcej losowych wartości):

   DROP TEMPORARY TABLE IF EXISTS rands;
   CREATE TEMPORARY TABLE rands ( rand_id INT );

    loop_me: LOOP
        IF cnt < 1 THEN
          LEAVE loop_me;
        END IF;

        INSERT INTO rands
           SELECT r1.id
             FROM random AS r1 JOIN
                  (SELECT (RAND() *
                                (SELECT MAX(id)
                                   FROM random)) AS id)
                   AS r2
            WHERE r1.id >= r2.id
            ORDER BY r1.id ASC
            LIMIT 1;

        SET cnt = cnt - 1;
      END LOOP loop_me;

W artykule rozwiązuje problem luk w identyfikatorach, powodując niezbyt losowe wyniki , utrzymując tabelę (używając wyzwalaczy itp. ... zobacz artykuł); Rozwiązuję problem, dodając kolejną kolumnę do tabeli, wypełnioną ciągłymi liczbami, zaczynając od 1 ( edycja: ta kolumna jest dodawana do tabeli tymczasowej utworzonej przez podzapytanie w czasie wykonywania, nie wpływa na twoją tabelę stałą):

   DROP TEMPORARY TABLE IF EXISTS rands;
   CREATE TEMPORARY TABLE rands ( rand_id INT );

    loop_me: LOOP
        IF cnt < 1 THEN
          LEAVE loop_me;
        END IF;

        SET @no_gaps_id := 0;

        INSERT INTO rands
           SELECT r1.id
             FROM (SELECT id, @no_gaps_id := @no_gaps_id + 1 AS no_gaps_id FROM random) AS r1 JOIN
                  (SELECT (RAND() *
                                (SELECT COUNT(*)
                                   FROM random)) AS id)
                   AS r2
            WHERE r1.no_gaps_id >= r2.id
            ORDER BY r1.no_gaps_id ASC
            LIMIT 1;

        SET cnt = cnt - 1;
      END LOOP loop_me;

W artykule widzę, że dołożył wszelkich starań, aby zoptymalizować kod; nie mam pojęcia, czy moje zmiany wpływają na wydajność, ale działają bardzo dobrze dla mnie.

bogdan
źródło
„nie mam pojęcia, czy / jak moje zmiany wpływają na wydajność” - całkiem sporo. Ponieważ @no_gaps_idnie można użyć indeksu, więc jeśli spojrzysz na EXPLAINswoje zapytanie, masz Using filesorti Using where(bez indeksu) dla podkwerend, w przeciwieństwie do pierwotnego zapytania.
Fabian Schmengler
2

Oto zmieniacz gier, który może być pomocny dla wielu;

Mam tabelę z 200 tys. Wierszy z sekwencyjnymi identyfikatorami , musiałem wybrać N losowych wierszy, więc wybieram generowanie losowych wartości na podstawie największego identyfikatora w tabeli, stworzyłem ten skrypt, aby dowiedzieć się, która jest najszybsza operacja:

logTime();
query("SELECT COUNT(id) FROM tbl");
logTime();
query("SELECT MAX(id) FROM tbl");
logTime();
query("SELECT id FROM tbl ORDER BY id DESC LIMIT 1");
logTime();

Wyniki są następujące:

  • Liczba: 36.8418693542479ms
  • Max: 0.241041183472ms
  • Zamówienie: 0.216960906982ms

W oparciu o te wyniki, opis zamówienia jest najszybszą operacją, aby uzyskać maksymalny identyfikator.
Oto moja odpowiedź na pytanie:

SELECT GROUP_CONCAT(n SEPARATOR ',') g FROM (
    SELECT FLOOR(RAND() * (
        SELECT id FROM tbl ORDER BY id DESC LIMIT 1
    )) n FROM tbl LIMIT 10) a

...
SELECT * FROM tbl WHERE id IN ($result);

FYI: Aby uzyskać 10 losowych wierszy z tabeli 200 000, zajęło mi 1,78 ms (w tym wszystkie operacje po stronie php)

António Almeida
źródło
3
Sugeruj LIMITnieznaczne zwiększenie - możesz uzyskać duplikaty.
Rick James
2

Wszystkie najlepsze odpowiedzi zostały już opublikowane (głównie te odnoszące się do linku http://jan.kneschke.de/projects/mysql/order-by-rand/ ).

Chcę wskazać inną możliwość przyspieszenia - buforowanie . Zastanów się, dlaczego potrzebujesz losowych wierszy. Prawdopodobnie chcesz wyświetlić losową wiadomość lub losową reklamę na stronie internetowej. Jeśli otrzymujesz 100 req / s, czy naprawdę potrzebne jest, aby każdy odwiedzający otrzymywał losowe wiersze? Zwykle buforowanie tych X losowych wierszy przez 1 sekundę (a nawet 10 sekund) jest całkowicie w porządku. Nie ma znaczenia, czy 100 unikalnych użytkowników w tej samej 1 sekundzie otrzyma te same losowe posty, ponieważ w następnej sekundzie kolejnych 100 odwiedzających otrzyma inny zestaw postów.

Korzystając z tego buforowania, możesz również użyć wolniejszego rozwiązania do pobierania losowych danych, ponieważ będą one pobierane z MySQL tylko raz na sekundę, niezależnie od twoich wymagań / wymagań.

Marki555
źródło
2

Jest to super szybki i jest w 100% losowy, nawet jeśli masz luki.

  1. Policz liczbę xdostępnych wierszySELECT COUNT(*) as rows FROM TABLE
  2. Wybierz 10 różnych liczb losowych a_1,a_2,...,a_10od 0 dox
  3. Zapytaj wiersze w ten sposób: SELECT * FROM TABLE LIMIT 1 offset a_idla i = 1, ..., 10

Znalazłem ten siekać w książce SQL Antipatterns z Bill Karwin .

Adam
źródło
Zastanawiałem się nad tym samym rozwiązaniem, powiedz mi, czy jest szybsze niż metoda innych?
G. Adnane
@ G.Adnane nie jest szybszy ani wolniejszy niż zaakceptowana odpowiedź, ale zaakceptowana odpowiedź zakłada równy rozkład identyfikatorów. Nie wyobrażam sobie żadnego scenariusza, w którym można to zagwarantować. To rozwiązanie znajduje się w O (1), gdzie SELECT column FROM table ORDER BY RAND() LIMIT 10jest w O (nlog (n)). Tak, to jest szybkie rozwiązanie i działa z każdą dystrybucją identyfikatorów.
Adam
nie, ponieważ w opublikowanym linku do zaakceptowanego rozwiązania istnieją inne metody, chcę wiedzieć, czy to rozwiązanie jest szybsze niż inne, w inny sposób możemy spróbować znaleźć inne, dlatego pytam w jakikolwiek sposób, +1 dla Twojej odpowiedzi. Korzystałem z
próbkowania
zdarza się, że chcesz uzyskać x liczby wierszy, ale przesunięcie następuje na końcu tabeli, która zwróci <x wierszy lub tylko 1 wiersz. nie widziałem twojej odpowiedzi przed wysłaniem mojej, ale wyjaśniłem tutaj stackoverflow.com/a/59981772/10387008
ZOLDIK
@ZOLDIK wydaje się, że wybierasz pierwsze 10 wierszy po przesunięciu x. Twierdziłbym, że nie jest to przypadkowa generacja 10 wierszy. W mojej odpowiedzi musisz wykonać kwerendę w kroku trzecim 10 razy, tj. Jeden dostaje tylko jeden wiersz na wykonanie i nie musisz się martwić, jeśli przesunięcie znajduje się na końcu tabeli.
Adam
1

Jeśli masz tylko jedno żądanie odczytu

Połącz odpowiedź @redsio z temp-table (600 K to niewiele):

DROP TEMPORARY TABLE IF EXISTS tmp_randorder;
CREATE TABLE tmp_randorder (id int(11) not null auto_increment primary key, data_id int(11));
INSERT INTO tmp_randorder (data_id) select id from datatable;

A następnie weź wersję @redsios Odpowiedź:

SELECT dt.*
FROM
       (SELECT (RAND() *
                     (SELECT MAX(id)
                        FROM tmp_randorder)) AS id)
        AS rnd
 INNER JOIN tmp_randorder rndo on rndo.id between rnd.id - 10 and rnd.id + 10
 INNER JOIN datatable AS dt on dt.id = rndo.data_id
 ORDER BY abs(rndo.id - rnd.id)
 LIMIT 1;

Jeśli stół jest duży, możesz przesiać w pierwszej części:

INSERT INTO tmp_randorder (data_id) select id from datatable where rand() < 0.01;

Jeśli masz wiele żądań odczytu

  1. Wersja: możesz zachować tabelę jako tmp_randordertrwałą, nazwij ją datatable_idlist. Odtworz ten stół w określonych odstępach czasu (dzień, godzina), ponieważ będzie on również dziury. Jeśli twój stół naprawdę się powiększy, możesz również uzupełnić dziury

    wybierz l.data_id jako całość z datatable_idlist l pozostało dołącz datatable dt na dt.id = l.data_id gdzie dt.id ma wartość null;

  2. Wersja: Nadaj swojemu zestawowi danych losową kolumnę sortowania bezpośrednio w tabeli danych lub w dodatkowej trwałej tabeli datatable_sortorder. Indeksuj tę kolumnę. Wygeneruj wartość losową w swojej aplikacji (nazywam ją $rand).

    select l.*
    from datatable l 
    order by abs(random_sortorder - $rand) desc 
    limit 1;

To rozwiązanie rozróżnia „rzędy brzegowe” od najwyższego i najniższego losowego sortera, więc zmieniaj je w odstępach czasu (raz dziennie).

flaschenpost
źródło
1

Innym prostym rozwiązaniem byłoby uszeregowanie wierszy i pobranie jednego z nich losowo, a dzięki temu rozwiązaniu nie będziesz musiał mieć żadnej kolumny opartej na „Id” w tabeli.

SELECT d.* FROM (
SELECT  t.*,  @rownum := @rownum + 1 AS rank
FROM mytable AS t,
    (SELECT @rownum := 0) AS r,
    (SELECT @cnt := (SELECT RAND() * (SELECT COUNT(*) FROM mytable))) AS n
) d WHERE rank >= @cnt LIMIT 10;

Możesz zmienić wartość graniczną zgodnie z potrzebą uzyskania dostępu do tylu wierszy, ile chcesz, ale najczęściej byłyby to kolejne wartości.

Jeśli jednak nie chcesz kolejnych losowych wartości, możesz pobrać większą próbkę i wybrać losowo z niej. coś jak ...

SELECT * FROM (
SELECT d.* FROM (
    SELECT  c.*,  @rownum := @rownum + 1 AS rank
    FROM buildbrain.`commits` AS c,
        (SELECT @rownum := 0) AS r,
        (SELECT @cnt := (SELECT RAND() * (SELECT COUNT(*) FROM buildbrain.`commits`))) AS rnd
) d 
WHERE rank >= @cnt LIMIT 10000 
) t ORDER BY RAND() LIMIT 10;
sactiw
źródło
1

Jednym ze sposobów, który uważam za całkiem dobry, jeśli istnieje identyfikator generowany automatycznie, jest użycie operatora modulo „%”. Na przykład, jeśli potrzebujesz 10 000 losowych rekordów na 70 000, możesz to uprościć, mówiąc, że potrzebujesz 1 na każde 7 wierszy. Można to uprościć w tym zapytaniu:

SELECT * FROM 
    table 
WHERE 
    id % 
    FLOOR(
        (SELECT count(1) FROM table) 
        / 10000
    ) = 0;

Jeśli wynik dzielenia wierszy docelowych przez sumę dostępnych nie jest liczbą całkowitą, będziesz mieć dodatkowe wiersze niż to, o co prosiłeś, więc powinieneś dodać klauzulę LIMIT, aby pomóc przyciąć zestaw wyników w następujący sposób:

SELECT * FROM 
    table 
WHERE 
    id % 
    FLOOR(
        (SELECT count(1) FROM table) 
        / 10000
    ) = 0
LIMIT 10000;

Wymaga to pełnego skanowania, ale jest szybsze niż ORDER BY RAND i moim zdaniem łatwiejsze do zrozumienia niż inne opcje wymienione w tym wątku. Również jeśli system zapisujący do DB tworzy zestawy wierszy w partiach, możesz nie otrzymać tak losowego wyniku, jak się spodziewałeś.

Nicolas Cohen
źródło
2
Teraz, gdy tak myślę, jeśli potrzebujesz losowych wierszy za każdym razem, gdy je wywołujesz, jest to bezużyteczne. Myślałem tylko o potrzebie pobrania losowych wierszy ze zbioru w celu przeprowadzenia badań. Nadal uważam, że modulo dobrze jest pomóc w innym przypadku. Możesz użyć modulo jako filtru pierwszego przejścia, aby obniżyć koszt operacji ORDER BY RAND.
Nicolas Cohen
1

Przejrzałem wszystkie odpowiedzi i nie sądzę, aby ktokolwiek w ogóle wspominał o tej możliwości i nie jestem pewien, dlaczego.

Jeśli chcesz maksymalnej prostoty i szybkości, przy niewielkich kosztach, wydaje mi się, że sensowne jest przechowywanie losowych liczb dla każdego wiersza w DB. Po prostu utwórz dodatkową kolumnę random_numberi ustaw ją domyślnie na RAND(). Utwórz indeks w tej kolumnie.

Następnie, gdy chcesz pobrać wiersz, wygeneruj losową liczbę w swoim kodzie (PHP, Perl, cokolwiek) i porównaj to z kolumną.

SELECT FROM tbl WHERE random_number >= :random LIMIT 1

Myślę, że chociaż jest to bardzo fajne dla jednego rzędu, dla dziesięciu rzędów takich jak OP poprosił, abyś musiał nazwać go dziesięć razy (lub wymyślić sprytne ulepszenie, które natychmiast mi ucieka)

Codemonkey
źródło
To jest naprawdę bardzo ładne i skuteczne podejście. Jedyną wadą jest to, że wymieniałeś przestrzeń na szybkość, co moim zdaniem wydaje się uczciwą transakcją.
Tochukwu Nkemdilim
Dzięki. Miałem scenariusz, w którym główny stół, z którego chciałem losowego rzędu, miał 5 milionów wierszy i całkiem sporo złączeń, a po wypróbowaniu większości podejść w tym pytaniu był to kludge, na którym się zdecydowałem. Jedna dodatkowa kolumna była dla mnie bardzo opłacalna.
Codemonkey
0

Poniższe informacje powinny być szybkie, obiektywne i niezależne od kolumny identyfikatora. Nie gwarantuje to jednak, że liczba zwróconych wierszy będzie zgodna z liczbą żądanych wierszy.

SELECT *
FROM t
WHERE RAND() < (SELECT 10 / COUNT(*) FROM t)

Objaśnienie: zakładając, że chcesz 10 wierszy na 100, wówczas każdy rząd ma 1/10 prawdopodobieństwa otrzymania WYBORU, co można osiągnąć WHERE RAND() < 0.1. Takie podejście nie gwarantuje 10 rzędów; ale jeśli zapytanie zostanie uruchomione wystarczającą ilość razy, średnia liczba wierszy na wykonanie wyniesie około 10, a każdy wiersz w tabeli zostanie wybrany równomiernie.

Salman A.
źródło
0

Możesz łatwo użyć losowego przesunięcia z limitem

PREPARE stm from 'select * from table limit 10 offset ?';
SET @total = (select count(*) from table);
SET @_offset = FLOOR(RAND() * @total);
EXECUTE stm using @_offset;

Możesz także zastosować taką klauzulę where

PREPARE stm from 'select * from table where available=true limit 10 offset ?';
SET @total = (select count(*) from table where available=true);
SET @_offset = FLOOR(RAND() * @total);
EXECUTE stm using @_offset;

Testowanie na 600 000 wierszy (700 MB) wykonanie zapytania do tabeli zajęło ~ 0,016 s Dysk HDD -

EDYCJA
   - Przesunięcie może przyjąć wartość zbliżoną do końca tabeli, co spowoduje, że instrukcja select zwróci mniej wierszy (a może tylko 1 wiersz), aby tego uniknąć, możemy to sprawdzić offsetponownie po zadeklarowaniu

SET @rows_count = 10;
PREPARE stm from "select * from table where available=true limit ? offset ?";
SET @total = (select count(*) from table where available=true);
SET @_offset = FLOOR(RAND() * @total);
SET @_offset = (SELECT IF(@total-@_offset<@rows_count,@_offset-@rows_count,@_offset));
SET @_offset = (SELECT IF(@_offset<0,0,@_offset));
EXECUTE stm using @rows_count,@_offset;
ZOLDIK
źródło
-1

Używam tego zapytania:

select floor(RAND() * (SELECT MAX(key) FROM table)) from table limit 10

czas zapytania: 0,016s

josejavierfm
źródło
Posiadanie PK takich jak 1,2,9,15. powyższym zapytaniem otrzymasz wiersze takie jak 4, 7, 14, 11, które są niewystarczające!
Junaid Atari
-2

Tak to robię:

select * 
from table_with_600k_rows
where rand() < 10/600000
limit 10

Podoba mi się, ponieważ nie wymaga innych tabel, jest łatwy do napisania i bardzo szybki do wykonania.

Bernardo Siu
źródło
5
To pełne skanowanie tabeli i nie używa żadnych indeksów. Dla dużych stołów i ruchliwego środowiska, które jest duże, nie, nie.
mat
-2

Użyj poniższego prostego zapytania, aby uzyskać losowe dane z tabeli.

SELECT user_firstname ,
COUNT(DISTINCT usr_fk_id) cnt
FROM userdetails 
GROUP BY usr_fk_id 
ORDER BY cnt ASC  
LIMIT 10
MANOJ
źródło
Jeśli chcesz użyć dowolnej instrukcji dołączenia i filtru, którego możesz użyć.
MANOJ
3
Z której części zapytania otrzymujesz losowość?
Marki555,
-4

Myślę, że to najlepszy możliwy sposób ..

SELECT id, id * RAND( ) AS random_no, first_name, last_name
FROM user
ORDER BY random_no
Ritesh Patadiya
źródło
8
Do diabła nie, to jeden z najgorszych sposobów na uzyskanie losowych wierszy ze stołu. To pełny skan tabeli + plik + tablica tmp = zła wydajność.
mat
1
Oprócz wydajności nie jest to również całkowicie przypadkowe; zamawiasz według iloczynu id i liczby losowej, zamiast po prostu sortować według liczby losowej, co oznacza, że ​​wiersze o niższych identyfikatorach będą tendencyjne do pojawienia się wcześniej w zestawie wyników.
Mark Amery