TABLESAMPLE PostgreSQL ma kilka dodatkowych zalet w porównaniu z innymi tradycyjnymi sposobami uzyskiwania losowych krotek.
TABLESAMPLE
jest klauzulą SQL SELECT i zapewnia dwie metody próbkowania, które są SYSTEM
i BERNOULLI
. Z pomocą TABLESAMPLE
możemy łatwo pobrać losowe wiersze z tabeli. Więcej informacji na temat TABLESAMPLE można znaleźć w poprzednim wpisie na blogu .
W tym poście na blogu porozmawiamy o alternatywnych sposobach uzyskania losowych wierszy. Jak ludzie wybierali losowe wiersze przed TABLESAMPLE
, jakie są zalety i wady innych metod i co zyskaliśmy dzięki TABLESAMPLE
?
Istnieją niesamowite posty na blogu dotyczące wybierania losowych wierszy, możesz zacząć czytać następujące posty na blogu, aby uzyskać dogłębne zrozumienie tego tematu.
Moje myśli o losowym rzędzie
Pobieranie losowych wierszy z tabeli bazy danych
random_agg()
Porównajmy tradycyjne sposoby uzyskiwania losowych wierszy z tabeli z nowymi sposobami zapewnianymi przez TABLESAMPLE.
Przed TABLESAMPLE
klauzula, były 3 powszechnie używane metody losowego wybierania wierszy z tabeli.
1- Uporządkuj losowo()
Do celów testowych musimy stworzyć tabelę i umieścić w niej trochę danych.
Utwórzmy tabelę ts_test i wstawmy do niej 1M wierszy:
CREATE TABLE ts_test (
id SERIAL PRIMARY KEY,
title TEXT
);
INSERT INTO ts_test (title)
SELECT
'Record #' || i
FROM
generate_series(1, 1000000) i;
Biorąc pod uwagę następującą instrukcję SQL do wyboru 10 losowych wierszy:
SELECT * FROM ts_test ORDER BY random() LIMIT 10;
Powoduje, że PostgreSQL wykonuje pełne skanowanie tabeli, a także porządkuje. Dlatego ta metoda nie jest preferowana w przypadku tabel z dużą liczbą wierszy ze względu na wydajność.
Przyjrzyjmy się EXPLAIN ANALYZE
wynik powyższego zapytania:
random=# EXPLAIN ANALYZE SELECT * FROM ts_test ORDER BY random() LIMIT 10;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------
Limit (cost=33959.03..33959.05 rows=10 width=36) (actual time=1956.786..1956.807 rows=10 loops=1)
-> Sort (cost=33959.03..35981.18 rows=808863 width=36) (actual time=1956.780..1956.789 rows=10 loops=1)
Sort Key: (random())
Sort Method: top-N heapsort Memory: 25kB
-> Seq Scan on ts_test (cost=0.00..16479.79 rows=808863 width=36) (actual time=0.276..1001.109 rows=1000000 loops=1)
Planning time: 1.434 ms
Execution time: 1956.900 ms
(7 rows)
Jako EXPLAIN ANALYZE
zaznacza, że wybranie 10 z 1 mln wierszy zajęło prawie 2 sekundy. Zapytanie używało również danych wyjściowych random()
jako klucz sortowania do uporządkowania wyników. Sortowanie wydaje się być tutaj najbardziej czasochłonnym zadaniem. Wykonajmy to za pomocą scenariusza za pomocą TABLESAMPLE
.
W PostgreSQL 9.5, aby uzyskać losowo dokładną liczbę wierszy, możemy użyć metody próbkowania SYSTEM_ROWS. Dostarczone przez tsm_system_rows
contrib, pozwala nam określić, ile wierszy ma zostać zwróconych przez próbkowanie. Zwykle tylko żądany procent można określić za pomocą TABLESAMPLE SYSTEM
i BERNOULLI
metody.
Najpierw powinniśmy utworzyć tsm_system_rows
rozszerzenie do korzystania z tej metody, ponieważ zarówno TABLESAMPLE SYSTEM
i TABLESAMPLE BERNOULLI
metody nie gwarantują, że podany procent da w wyniku oczekiwaną liczbę wierszy. Proszę sprawdzić poprzednią TABLESAMPLE p ost, żeby przypomnieć sobie, dlaczego tak działają.
Zacznijmy od utworzenia tsm_system_rows
rozszerzenie:
random=# CREATE EXTENSION tsm_system_rows;
CREATE EXTENSION
Teraz porównajmy „ORDER BY random()
” EXPLAIN ANALYZE
wyjście z EXPLAIN ANALYZE
wyjście tsm_system_rows
zapytanie, które zwraca 10 losowych wierszy z 1 mln wierszy.
random=# EXPLAIN ANALYZE SELECT * FROM ts_test TABLESAMPLE SYSTEM_ROWS(10);
QUERY PLAN
-------------------------------------------------------------------------------------------------------
Sample Scan on ts_test (cost=0.00..4.10 rows=10 width=18) (actual time=0.069..0.083 rows=10 loops=1)
Sampling: system_rows ('10'::bigint)
Planning time: 0.646 ms
Execution time: 0.159 ms
(4 rows)
Całe zapytanie zajęło 0,159 ms. Ta metoda próbkowania jest niezwykle szybka w porównaniu z „ORDER BY random()
” metoda, która zajęła 1956,9 ms.
2- Porównaj z random()
Poniższy kod SQL pozwala nam pobierać losowe wiersze z prawdopodobieństwem 10%
SELECT * FROM ts_test WHERE random() <= 0.1;
Ta metoda jest szybsza niż zamawianie losowe, ponieważ nie sortuje wybranych wierszy. Zwróci przybliżony procent wierszy z tabeli, podobnie jak BERNOULLI
lub SYSTEM
TABLESAMPLE
metody.
Sprawdźmy EXPLAIN ANALYZE
wyjście random()
zapytanie powyżej:
random=# EXPLAIN ANALYZE SELECT * FROM ts_test WHERE random() <= 0.1;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------
Seq Scan on ts_test (cost=0.00..21369.00 rows=333333 width=18) (actual time=0.089..280.483 rows=100014 loops=1)
Filter: (random() <= '0.1'::double precision)
Rows Removed by Filter: 899986
Planning time: 0.704 ms
Execution time: 367.527 ms
(5 rows)
Zapytanie trwało 367,5 ms. Znacznie lepiej niż ORDER BY random()
. Ale trudniej jest kontrolować dokładną liczbę rzędów. Porównajmy „random()
” EXPLAIN ANALYZE
wyjście z EXPLAIN ANALYZE
wyjście TABLESAMPLE BERNOULLI
zapytanie, które zwraca około 10% losowych wierszy z tabeli 1M wierszy.
random=# EXPLAIN ANALYZE SELECT * FROM ts_test TABLESAMPLE BERNOULLI (10);
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------
Sample Scan on ts_test (cost=0.00..7369.00 rows=100000 width=18) (actual time=0.015..147.002 rows=100155 loops=1)
Sampling: bernoulli ('10'::real)
Planning time: 0.076 ms
Execution time: 239.289 ms
(4 rows)
Daliśmy 10 jako parametr BERNOULLI
ponieważ potrzebujemy 10% wszystkich rekordów.
Tutaj widzimy, że BERNOULLI
wykonanie tej metody zajęło 239,289 ms. Te dwie metody są dość podobne pod względem sposobu działania, BERNOULLI
jest nieco szybszy, ponieważ losowy wybór odbywa się na niższym poziomie. Jedna z zalet korzystania z BERNOULLI
w porównaniu do WHERE random() <= 0.1
jest REPEATABLE
klauzula, którą opisaliśmy w poprzednim TABLESAMPLE
post.
3- Dodatkowa kolumna z losową wartością
Ta metoda sugeruje dodanie nowej kolumny z losowo przypisanymi wartościami, dodanie do niej indeksu, wykonanie sortowania według tej kolumny i opcjonalnie okresowe aktualizowanie ich wartości w celu randomizacji rozkładu.
Ta strategia pozwala w większości na powtarzalne losowe próbkowanie. Działa znacznie szybciej niż pierwsza metoda, ale jej konfiguracja po raz pierwszy wymaga wysiłku i skutkuje kosztami wydajności operacji wstawiania, aktualizacji i usuwania.
Zastosujmy tę metodę w naszym ts_test
tabela.
Najpierw dodamy nową kolumnę o nazwie randomcolumn
z typem podwójnej precyzji, ponieważ random()
w PostgreSQL funkcja zwraca liczbę z podwójną precyzją.
random=# ALTER TABLE ts_test ADD COLUMN randomcolumn DOUBLE PRECISION;
ALTER TABLE
Następnie zaktualizujemy nową kolumnę za pomocą random()
funkcja.
random=# \timing
Timing is on.
random=# BEGIN;
BEGIN
Time: 2.071 ms
random=# UPDATE ts_test SET randomcolumn = RANDOM();
UPDATE 1000000
Time: 8483.741 ms
random=# COMMIT;
COMMIT
Time: 2.615 ms
Ta metoda ma początkowy koszt utworzenia nowej kolumny i wypełnienia tej nowej kolumny wartościami losowymi (0,0-1,0), ale jest to koszt jednorazowy. W tym przykładzie dla 1 mln wierszy zajęło to prawie 8,5 sekundy.
Spróbujmy zaobserwować, czy nasze wyniki są odtwarzalne, wysyłając zapytanie o 100 wierszy naszą nową metodą:
random=# SELECT * FROM ts_test ORDER BY randomcolumn LIMIT 100;
-------+---------------+----------------------
13522 | Record #13522 | 6.4261257648468e-08
671584 | Record #671584 | 6.4261257648468e-07
714012 | Record #714012 | 1.95764005184174e-06
162016 | Record #162016 | 3.44449654221535e-06
106867 | Record #106867 | 3.66196036338806e-06
865669 | Record #865669 | 3.96883115172386e-06
927 | Record #927 | 4.65428456664085e-06
526017 | Record #526017 | 4.65987250208855e-06
98338 | Record #98338 | 4.91179525852203e-06
769625 | Record #769625 | 4.91319224238396e-06
...
462484 | Record #462484 | 9.83504578471184e-05
(100 rows)
Kiedy wykonujemy powyższe zapytanie, w większości otrzymujemy ten sam zestaw wyników, ale nie jest to gwarantowane, ponieważ użyliśmy random()
funkcja do wypełniania randomcolumn
wartości iw tym przypadku więcej niż jedna kolumna może mieć tę samą wartość. Aby mieć pewność, że otrzymujemy te same wyniki przy każdym uruchomieniu, powinniśmy ulepszyć nasze zapytanie, dodając kolumnę ID do ORDER BY
klauzuli, w ten sposób zapewniamy, że ORDER BY
klauzula określa unikalny zestaw wierszy, ponieważ kolumna id zawiera indeks klucza podstawowego.
Teraz uruchommy ulepszone zapytanie poniżej:
random=# SELECT * FROM ts_test ORDER BY randomcolumn, id LIMIT 100;
id | title | randomcolumn
--------+----------------+----------------------
13522 | Record #13522 | 6.4261257648468e-08
671584 | Record #671584 | 6.4261257648468e-07
714012 | Record #714012 | 1.95764005184174e-06
162016 | Record #162016 | 3.44449654221535e-06
106867 | Record #106867 | 3.66196036338806e-06
865669 | Record #865669 | 3.96883115172386e-06
927 | Record #927 | 4.65428456664085e-06
526017 | Record #526017 | 4.65987250208855e-06
98338 | Record #98338 | 4.91179525852203e-06
769625 | Record #769625 | 4.91319224238396e-06
...
462484 | Record #462484 | 9.83504578471184e-05
(100 rows)
Teraz jesteśmy pewni, że za pomocą tej metody możemy uzyskać powtarzalną próbkę losową.
Czas zobaczyć EXPLAIN ANALYZE
wyniki naszego końcowego zapytania:
random=# EXPLAIN ANALYZE SELECT * FROM ts_test ORDER BY randomcolumn, id LIMIT 100;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------
Limit (cost=55571.28..55571.53 rows=100 width=26) (actual time=1951.811..1952.039 rows=100 loops=1)
-> Sort (cost=55571.28..58071.28 rows=1000000 width=26) (actual time=1951.804..1951.891 rows=100 loops=1)
Sort Key: randomcolumn, id
Sort Method: top-N heapsort Memory: 32kB
-> Seq Scan on ts_test (cost=0.00..17352.00 rows=1000000 width=26) (actual time=0.058..995.011 rows=1000000 loops=1)
Planning time: 0.481 ms
Execution time: 1952.215 ms
(7 rows)
Do porównania tej metody z TABLESAMPLE
metody, wybierzmy SYSTEM
metoda z REPEATABLE
opcja, ponieważ ta metoda daje nam powtarzalne wyniki.
TABLESAMPLE
SYSTEM
metoda zasadniczo wykonuje próbkowanie na poziomie bloku/strony; odczytuje i zwraca losowe strony z dysku. W ten sposób zapewnia losowość na poziomie strony zamiast na poziomie krotki. Dlatego trudno jest kontrolować, czy pobierane rzędy liczą się dokładnie. Jeśli nie zostaną wybrane żadne strony, możemy nie uzyskać żadnego wyniku. Próbkowanie na poziomie strony jest również podatne na efekt grupowania.
TABLESAMPLE
SKŁADNIA jest taka sama dla BERNOULLI
i SYSTEM
metod, określimy procent jak zrobiliśmy w BERNOULLI
metoda.
Szybkie przypomnienie: SKŁADNIA
SELECT select_expression
FROM table_name
TABLESAMPLE sampling_method ( argument [, ...] ) [ REPEATABLE ( seed ) ]
...
Aby pobrać około 100 wierszy, musimy zażądać 100 * 100 / 1 000 000 =0,01% wierszy tabeli. Więc nasz procent wyniesie 0,01.
Przed rozpoczęciem zapytania pamiętajmy, jak REPEATABLE
Pracuje. Zasadniczo wybierzemy parametr seed i otrzymamy te same wyniki za każdym razem, gdy użyjemy tego samego seeda w poprzednim zapytaniu. Jeśli dostarczymy inne ziarno, zapytanie wygeneruje zupełnie inny zestaw wyników.
Spróbujmy zobaczyć wyniki za pomocą zapytań.
random=# Select * from ts_test tablesample system (0.01) repeatable (60);
id | title | randomcolumn
--------+----------------+---------------------
659598 | Record #659598 | 0.964113113470376
659599 | Record #659599 | 0.531714483164251
659600 | Record #659600 | 0.477636905387044
659601 | Record #659601 | 0.861940925940871
659602 | Record #659602 | 0.545977566856891
659603 | Record #659603 | 0.326795583125204
659604 | Record #659604 | 0.469275736715645
659605 | Record #659605 | 0.734953186474741
659606 | Record #659606 | 0.41613544523716
...
659732 | Record #659732 | 0.893704727292061
659733 | Record #659733 | 0.847225237637758
(136 rows)
Otrzymujemy 136 wierszy, ponieważ można uznać, że liczba ta zależy od tego, ile wierszy jest przechowywanych na jednej stronie.
Teraz spróbujmy uruchomić to samo zapytanie z tym samym numerem inicjatora:
random=# Select * from ts_test tablesample system (0.01) repeatable (60);
id | title | randomcolumn
--------+----------------+---------------------
659598 | Record #659598 | 0.964113113470376
659599 | Record #659599 | 0.531714483164251
659600 | Record #659600 | 0.477636905387044
659601 | Record #659601 | 0.861940925940871
659602 | Record #659602 | 0.545977566856891
659603 | Record #659603 | 0.326795583125204
659604 | Record #659604 | 0.469275736715645
659605 | Record #659605 | 0.734953186474741
659606 | Record #659606 | 0.41613544523716
...
659732 | Record #659732 | 0.893704727292061
659733 | Record #659733 | 0.847225237637758
(136 rows)
Ten sam zestaw wyników otrzymujemy dzięki REPEATABLE
opcja. Teraz możemy porównać TABLESAMPLE SYSTEM
metoda z losową metodą kolumn, patrząc na EXPLAIN ANALYZE
wyjście.
random=# EXPLAIN ANALYZE SELECT * FROM ts_test TABLESAMPLE SYSTEM (0.01) REPEATABLE (60);
QUERY PLAN
---------------------------------------------------------------------------------------------------------
Sample Scan on ts_test (cost=0.00..5.00 rows=100 width=26) (actual time=0.091..0.230 rows=136 loops=1)
Sampling: system ('0.01'::real) REPEATABLE ('60'::double precision)
Planning time: 0.323 ms
Execution time: 0.398 ms
(4 rows)
SYSTEM
Metoda wykorzystuje skan próbki i jest niezwykle szybka. Jedynym przeciwieństwem tej metody jest to, że nie możemy zagwarantować, że podany procent da oczekiwaną liczbę wierszy.
Wniosek
W tym poście na blogu porównaliśmy standardowe TABLESAMPLE
(SYSTEM
, BERNOULLI
) i tsm_system_rows
moduł ze starszymi metodami losowymi.
Tutaj możesz szybko przejrzeć wyniki:
ORDER BY random()
jest powolny z powodu sortowaniarandom() <= 0.1
jest podobny doBERNOULLI
metoda- Dodanie kolumny z losową wartością wymaga wstępnej pracy i może prowadzić do problemów z wydajnością
SYSTEM
jest szybki, ale trudno kontrolować liczbę rzędówtsm_system_rows
jest szybki i może kontrolować liczbę rzędów
W rezultacie tsm_system_rows
bije na głowę każdą inną metodę wyboru tylko kilku losowych wierszy.
Ale prawdziwym zwycięzcą jest zdecydowanie TABLESAMPLE
samo. Dzięki swojej rozszerzalności niestandardowe rozszerzenia (np. tsm_system_rows
, tsm_system_time
) można opracować za pomocą TABLESAMPLE
funkcje metody.
Uwaga programisty: Więcej informacji na temat pisania własnych metod próbkowania można znaleźć w rozdziale Writing A Table Sampling Method w dokumentacji PostgreSQL.
Uwaga na przyszłość: Omówimy projekt AXLE i rozszerzenie tsm_system_time w naszym następnym TABLESAMPLE
wpis na blogu.