Chcę losowy wybór wierszy w PostgreSQL, próbowałem tego:
select * from table where random() < 0.01;
Ale niektórzy inni polecają to:
select * from table order by random() limit 1000;
Mam bardzo duży stół z 500 milionami rzędów, chcę, żeby był szybki.
Które podejście jest lepsze? Jakie są różnice? Jaki jest najlepszy sposób wyboru losowych wierszy?
sql
performance
postgresql
random
nanounanue
źródło
źródło
Odpowiedzi:
Biorąc pod uwagę specyfikację (plus dodatkowe informacje w komentarzach),
Poniższe zapytanie nie wymaga sekwencyjnego skanowania dużej tabeli, tylko skanowanie indeksu.
Najpierw uzyskaj oszacowania dla głównego zapytania:
Jedyną prawdopodobnie kosztowną częścią jest
count(*)
(dla dużych stołów). Biorąc pod uwagę powyższe specyfikacje, nie potrzebujesz go. Kosztorys będzie wystarczający, dostępny prawie za darmo ( szczegółowe wyjaśnienie tutaj ):Tak długo, jak
ct
nie jest dużo mniejszyid_span
, zapytanie będzie przewyższać inne podejścia.Generuj losowe liczby w
id
przestrzeni. Masz „kilka luk”, więc dodaj 10% (wystarczająco, aby łatwo zakryć puste miejsca) do liczby wierszy do odzyskania.Każdą z nich
id
można wybrać wiele razy przypadkowo (choć jest to mało prawdopodobne przy dużej przestrzeni identyfikatora), więc zgrupuj wygenerowane liczby (lub użyjDISTINCT
).Dołącz
id
do dużego stołu. Powinno to być bardzo szybkie przy założonym indeksie.Na koniec przytnij nadwyżki
id
, które nie zostały zjedzone przez duplikaty i luki. Każdy rząd ma całkowicie taką samą szansę na wybranie.Krótka wersja
Możesz uprościć to zapytanie. CTE w powyższym zapytaniu służy wyłącznie celom edukacyjnym:
Udoskonal za pomocą rCTE
Zwłaszcza jeśli nie masz pewności co do luk i szacunków.
Możemy pracować z mniejszą nadwyżką w zapytaniu podstawowym. Jeśli jest zbyt wiele luk, więc nie znajdziemy wystarczającej liczby wierszy w pierwszej iteracji, rCTE kontynuuje iterację z terminem rekurencyjnym. Wciąż potrzebujemy stosunkowo niewielkich przerw w przestrzeni ID lub rekursja może wyschnąć przed osiągnięciem limitu - lub musimy zacząć od wystarczająco dużego bufora, który nie pozwala na optymalizację wydajności.
Duplikaty są eliminowane przez
UNION
w rCTE.Zewnętrzne
LIMIT
powoduje, że CTE zatrzymuje się, gdy tylko mamy wystarczającą liczbę rzędów.To zapytanie jest starannie opracowane, aby użyć dostępnego indeksu, wygenerować faktycznie losowe wiersze i nie zatrzymywać się, dopóki nie osiągniemy limitu (chyba że rekursja nie będzie sucha). Istnieje wiele pułapek, jeśli zamierzasz go przepisać.
Zawiń w funkcję
Do wielokrotnego użytku z różnymi parametrami:
Połączenie:
Możesz nawet sprawić, by ten ogólny działał dla dowolnej tabeli: Weź nazwę kolumny PK i tabeli jako typ polimorficzny i użyj
EXECUTE
... Ale to wykracza poza zakres tego pytania. Widzieć:Możliwa alternatywa
JEŻELI twoje wymagania zezwalają na identyczne zestawy dla powtarzanych połączeń (a mówimy o powtarzanych połączeniach), rozważyłbym widok zmaterializowany . Wykonaj powyższe zapytanie raz i zapisz wynik w tabeli. Użytkownicy otrzymują quasi-losowy wybór z prędkością błyskawicy. Odśwież swój losowy wybór w odstępach czasu lub wybranych wydarzeniach.
Wprowadzono Postgres 9.5
TABLESAMPLE SYSTEM (n)
Gdzie
n
jest procent. Instrukcja:Odważny nacisk moje. Jest bardzo szybki , ale wynik nie jest dokładnie losowy . Instrukcja ponownie:
Liczba zwracanych wierszy może się znacznie różnić. W naszym przykładzie, aby uzyskać około 1000 wierszy:
Związane z:
Lub zainstaluj dodatkowy moduł tsm_system_rows, aby dokładnie uzyskać liczbę żądanych wierszy (jeśli jest ich wystarczająco dużo) i umożliwić wygodniejszą składnię:
Szczegółowe informacje można znaleźć w odpowiedzi Evana .
Ale to wciąż nie jest przypadkowe.
źródło
JOIN bigtbl t
co jest skrótem odJOIN bigtbl AS t
.t
to alias tabeli dlabigtbl
. Jego celem jest skrócenie składni, ale nie byłoby to konieczne w tym konkretnym przypadku. W mojej odpowiedzi uprościłem zapytanie i dodałem prostą wersję.Możesz sprawdzić i porównać plan wykonania obu za pomocą
Szybki test na dużym stole 1 pokazuje, że
ORDER BY
pierwszy sortuje cały stół, a następnie wybiera pierwsze 1000 przedmiotów. Sortowanie dużej tabeli nie tylko czyta tę tabelę, ale obejmuje również odczytywanie i zapisywanie plików tymczasowych.where random() < 0.1
Tylko skanuje całą tabelę raz.W przypadku dużych tabel może to nie być pożądane, ponieważ nawet jedno pełne skanowanie tabeli może potrwać długo.
Trzecia propozycja byłaby
Ten zatrzymuje skanowanie tabeli, jak tylko zostanie znalezione 1000 wierszy, i dlatego wraca wcześniej. Oczywiście to trochę zmniejsza losowość, ale być może jest to wystarczające w twoim przypadku.
Edycja: Oprócz tych rozważań możesz sprawdzić już zadane pytania. Użycie zapytania
[postgresql] random
zwraca sporo trafień.I powiązany artykuł o depezie opisujący kilka innych podejść:
1 „duży” jak w „cała tabela nie zmieści się w pamięci”.
źródło
random() < 0.02
a następnie przetasować tę listęlimit 1000
! Sortowanie będzie tańsze w kilku tysiącach rzędów (lol).posgresql order by random (), wybierz wiersze w losowej kolejności:
kolejność postgresql losowo () z wyraźnym:
zamówienie postgresql według losowego limitu jednego wiersza:
źródło
select your_columns from your_table ORDER BY random() limit 1
wykonuj ~ 2 minuty, aby wykonać 45 mil wierszyPocząwszy od PostgreSQL 9.5, dostępna jest nowa składnia przeznaczona do pobierania losowych elementów z tabeli:
Ten przykład da ci 5% elementów z
mytable
.Zobacz więcej wyjaśnień w tym poście na blogu: http://www.postgresql.org/docs/current/static/sql-select.html
źródło
TABLESAMPLE SYSTEM_ROWS(400)
aby uzyskać próbkę 400 losowych wierszy. Aby korzystać z tej instrukcji, musisz włączyć wbudowanetsm_system_rows
rozszerzenie .Ten z ORDER BY będzie wolniejszy.
select * from table where random() < 0.01;
zapisuje rekord po rekordzie i decyduje o losowym filtrowaniu go lub nie. Stanie się tak,O(N)
ponieważ wystarczy sprawdzić każdy rekord tylko raz.select * from table order by random() limit 1000;
posortuje cały stół, a następnie wybierze pierwsze 1000. Oprócz magii voodoo za kulisami, kolejność jestO(N * log N)
.Minusem tego
random() < 0.01
jest to, że otrzymasz zmienną liczbę rekordów wyjściowych.Uwaga: istnieje lepszy sposób na tasowanie zestawu danych niż sortowanie losowe: losowanie Fisher-Yates , które jest uruchamiane
O(N)
. Implementacja przetasowania w SQL wydaje się jednak sporym wyzwaniem.źródło
Oto decyzja, która działa dla mnie. Myślę, że to bardzo proste do zrozumienia i wykonania.
źródło
ORDER BY random()
działa, ale może nie być wydajne podczas pracy z dużym stołem.Jeśli wiesz, ile wierszy chcesz, sprawdź
tsm_system_rows
.tsm_system_rows
Najpierw zainstaluj rozszerzenie
Twoje zapytanie
źródło
SYSTEM
metody.tsm_system_rows
itsm_system_time
rozszerzeń. O ile widzę, są one praktycznie bezużyteczne do niczego poza absolutnie minimalnym wyborem losowych wierszy. Byłbym wdzięczny, gdyby mógł Pan rzucić okiem i skomentować ważność mojej analizy lub w inny sposób.Jeśli chcesz tylko jeden wiersz, możesz użyć obliczenia
offset
wyprowadzonego zcount
.źródło
Możliwa jest odmiana zmaterializowanego widoku „Możliwa alternatywa” nakreślonego przez Erwina Brandstettera .
Powiedz na przykład, że nie chcesz duplikatów zwracanych losowych wartości. Musisz więc ustawić wartość logiczną w tabeli podstawowej zawierającej (nielosowy) zestaw wartości.
Zakładając, że jest to tabela wejściowa:
W
ID_VALUES
razie potrzeby wypełnij tabelę. Następnie, zgodnie z opisem Erwina, utwórz zmaterializowany widok, któryID_VALUES
raz losuje tabelę:Zauważ, że zmaterializowany widok nie zawiera użytej kolumny, ponieważ szybko stanie się nieaktualna. Widok nie musi także zawierać innych kolumn, które mogą znajdować się w
id_values
tabeli.W celu uzyskania (i „zużywać”) wartości losowych, wymaga modernizacji-ZWROTU o
id_values
wybranieid_values
zid_values_randomized
z łączeniem i zastosowanie odpowiednich kryteriów uzyskać tylko istotne możliwości. Na przykład:Zmień
LIMIT
w razie potrzeby - jeśli potrzebujesz tylko jednej losowej wartości naraz, zmieńLIMIT
na1
.Przy odpowiednich indeksach
id_values
uważam, że UPDATE-RETURNING powinien być wykonywany bardzo szybko przy niewielkim obciążeniu. Zwraca losowe wartości z jedną bazą danych w obie strony. Kryteria dla „kwalifikujących się” wierszy mogą być tak złożone, jak to konieczne. Nowe wiersze można dodawać doid_values
tabeli w dowolnym momencie i będą one dostępne dla aplikacji, gdy tylko zmaterializowany widok zostanie odświeżony (co może być prawdopodobnie uruchomione poza godzinami szczytu). Tworzenie i odświeżanie zmaterializowanego widoku będzie powolne, ale trzeba go wykonać tylko wtedy, gdy nowe identyfikatory zostaną dodane doid_values
tabeli.źródło
Jedna lekcja z mojego doświadczenia:
offset floor(random() * N) limit 1
nie jest szybszy niżorder by random() limit 1
.Myślałem, że
offset
podejście będzie szybsze, ponieważ powinno zaoszczędzić czas sortowania w Postgres. Okazuje się, że nie było.źródło
Dodaj kolumnę o nazwie
r
z typemserial
. Indexr
.Załóżmy, że mamy 200 000 wierszy, wygenerujemy liczbę losową
n
, gdzie 0n
<<= 200 000.Wybierz wiersze za pomocą
r > n
, posortuj jeASC
i wybierz najmniejszy.Kod:
Kod jest zrozumiały. Podzapytanie w środku służy do szybkiego oszacowania liczby wierszy tabeli z https://stackoverflow.com/a/7945274/1271094 .
Na poziomie aplikacji musisz ponownie wykonać instrukcję, jeśli
n
> liczba wierszy lub musisz wybrać wiele wierszy.źródło
Wiem, że jestem trochę spóźniony na imprezę, ale właśnie znalazłem to niesamowite narzędzie o nazwie pg_sample :
Próbowałem tego z bazą danych o wielkości 350 mln wierszy i było to naprawdę szybkie, nie wiem o losowości .
źródło