PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Próbka tabel i inne metody uzyskiwania losowych krotek

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 SYSTEMBERNOULLI 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 sortowania
  • random() <= 0.1 jest podobny do BERNOULLI 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ów
  • tsm_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.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Nazwa tabeli lub kolumny nie może zaczynać się od cyfry?

  2. django.db.utils.OperationalError Nie można połączyć się z serwerem

  3. Kolumna „mary” nie istnieje

  4. Eksportuj dane tabeli Postgresql za pomocą pgAdmin

  5. IntegrityError:rozróżnij unikalne ograniczenie od niezerowych naruszeń