sql >> Databáze >  >> RDS >> PostgreSQL

Tabulka a další metody pro získání náhodných n-tic

TABLESAMPLE PostgreSQL přináší několik dalších výhod ve srovnání s jinými tradičními způsoby získávání náhodných n-tic.

TABLESAMPLE je klauzule SQL SELECT a poskytuje dvě metody vzorkování, které jsou SYSTEM a BERNOULLI . S pomocí TABLESAMPLE můžeme snadno načíst náhodné řádky z tabulky. Další informace o TABLESAMPLE najdete v předchozím příspěvku na blogu .

V tomto příspěvku na blogu budeme hovořit o alternativních způsobech získávání náhodných řádků. Jak lidé vybírali náhodné řádky před TABLESAMPLE , jaké jsou výhody a nevýhody ostatních metod a co jsme získali pomocí TABLESAMPLE ?

Existují úžasné blogové příspěvky o výběru náhodných řádků. Můžete začít číst následující blogové příspěvky, abyste tomuto tématu porozuměli.

My Thoughts of Getting Random Row

Získávání náhodných řádků z databázové tabulky

random_agg()

Porovnejme tradiční způsoby získávání náhodných řádků z tabulky s novými způsoby, které poskytuje TABLESAMPLE.

Před TABLESAMPLE klauzule, existovaly 3 běžně používané metody pro náhodný výběr řádků z tabulky.

1- Řadit podle random()

Pro účely testování musíme vytvořit tabulku a vložit do ní nějaká data.

Vytvořme tabulku ts_test a vložíme do ní 1M řádků:

CREATE TABLE ts_test (
  id SERIAL PRIMARY KEY,
  title TEXT
);

INSERT INTO ts_test (title)
SELECT
    'Record #' || i
FROM
    generate_series(1, 1000000) i;

S ohledem na následující příkaz SQL pro výběr 10 náhodných řádků:

SELECT * FROM ts_test ORDER BY random() LIMIT 10;

Způsobí, že PostgreSQL provede úplné skenování tabulky a také objednávání. Proto tato metoda není preferována pro tabulky s velkým počtem řádků z důvodu výkonu.

Podívejme se na EXPLAIN ANALYZE výstup tohoto dotazu výše:

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 zdůrazňuje, že výběr 10 z 1 milionu řádků trval téměř 2 sekundy. Dotaz také používal výstup random() jako klíč řazení pro řazení výsledků. Časově nejnáročnějším úkolem se zde zdá být třídění. Proveďme to pomocí scénáře pomocí TABLESAMPLE .

V PostgreSQL 9.5, pro získání přesného počtu řádků náhodně, můžeme použít metodu vzorkování SYSTEM_ROWS. Poskytuje tsm_system_rows contrib nám umožňuje určit, kolik řádků má být vráceno vzorkováním. Normálně lze pomocí TABLESAMPLE SYSTEM zadat pouze požadované procento a BERNOULLI metody.

Nejprve bychom měli vytvořit tsm_system_rows rozšíření pro použití této metody, protože TABLESAMPLE SYSTEM a TABLESAMPLE BERNOULLI metody nezaručují, že poskytnuté procento povede k očekávanému počtu řádků. Zkontrolujte prosím předchozí TABULKY P ost, aby si vzpomněli, proč tak fungují.

Začněme vytvořením tsm_system_rows rozšíření:

random=# CREATE EXTENSION tsm_system_rows;
CREATE EXTENSION

Nyní porovnejme „ORDER BY random()EXPLAIN ANALYZE výstup pomocí EXPLAIN ANALYZE výstup tsm_system_rows dotaz, který vrátí 10 náhodných řádků z 1 milionové tabulky řádků.

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)

Celý dotaz trval 0,159 ms. Tato metoda vzorkování je extrémně rychlá ve srovnání s „ORDER BY random() ” metoda, která trvala 1956,9 ms.

2- Porovnat s random()

Následující SQL nám umožňuje získat náhodné řádky s 10% pravděpodobností

SELECT * FROM ts_test WHERE random() <= 0.1;

Tato metoda je rychlejší než náhodné řazení, protože netřídí vybrané řádky. Vrátí přibližné procento řádků z tabulky stejně jako BERNOULLI nebo SYSTEM TABLESAMPLE metody.

Podívejme se na EXPLAIN ANALYZE výstup random() dotaz výše:

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)

Dotaz trval 367,5 ms. Mnohem lepší než ORDER BY random() . Ale je těžší kontrolovat přesný počet řádků. Porovnejme „random()EXPLAIN ANALYZE výstup pomocí EXPLAIN ANALYZE výstup TABLESAMPLE BERNOULLI dotaz, který vrátí přibližně 10 % náhodných řádků z tabulky 1M řádků.

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)

Parametr BERNOULLI jsme dali 10 protože potřebujeme 10 % všech záznamů.

Zde vidíme, že BERNOULLI provedení metody trvalo 239,289 ms. Tyto dvě metody jsou si velmi podobné v tom, jak fungují, BERNOULLI je o něco rychlejší, protože náhodný výběr se vše provádí na nižší úrovni. Jednou z výhod použití BERNOULLI ve srovnání s WHERE random() <= 0.1 je REPEATABLE klauzuli, kterou jsme popsali v předchozím TABLESAMPLE příspěvek.

3- Extra sloupec s náhodnou hodnotou

Tato metoda navrhuje přidat nový sloupec s náhodně přiřazenými hodnotami, přidat k němu index, provést řazení podle tohoto sloupce a volitelně pravidelně aktualizovat jejich hodnoty, aby se distribuce náhodně upravila.

Tato strategie umožňuje většinou opakovatelné náhodné vzorkování. Funguje mnohem rychleji než první metoda, ale její první nastavení vyžaduje úsilí a výsledkem jsou náklady na výkon operací vkládání, aktualizace a mazání.

Aplikujme tuto metodu na náš ts_test tabulka.

Nejprve přidáme nový sloupec s názvem randomcolumn s typem double precision, protože PostgreSQL random() funkce vrací číslo s dvojnásobnou přesností.

random=# ALTER TABLE ts_test ADD COLUMN randomcolumn DOUBLE PRECISION;
ALTER TABLE

Poté aktualizujeme nový sloupec pomocí random() funkce.

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

Tato metoda má počáteční náklady na vytvoření nového sloupce a naplnění tohoto nového sloupce náhodnými hodnotami (0,0–1,0), ale jde o jednorázové náklady. V tomto příkladu to u 1 milionu řádků trvalo téměř 8,5 sekundy.

Pokusme se zjistit, zda jsou naše výsledky reprodukovatelné, dotazem na 100 řádků naší novou metodou:

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)

Když provedeme výše uvedený dotaz, většinou dostaneme stejnou sadu výsledků, ale to není zaručeno, protože jsme použili random() funkce pro vyplnění randomcolumn hodnoty a v tomto případě může mít stejnou hodnotu více než jeden sloupec. Abychom si byli jisti, že při každém spuštění získáme stejné výsledky, měli bychom náš dotaz vylepšit přidáním sloupce ID do ORDER BY klauzule, tímto způsobem zajistíme, že ORDER BY klauzule určuje jedinečnou sadu řádků, protože sloupec id má index primárního klíče.

Nyní spustíme vylepšený dotaz níže:

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)

Nyní jsme si jisti, že pomocí této metody můžeme získat reprodukovatelný náhodný vzorek.

Je čas podívat se na EXPLAIN ANALYZE výsledky našeho posledního dotazu:

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)

Pro porovnání této metody s TABLESAMPLE metody, vybereme SYSTEM metodou REPEATABLE možnost, protože tato metoda nám poskytuje reprodukovatelné výsledky.

TABLESAMPLE SYSTEM metoda v zásadě provádí vzorkování na úrovni bloku/stránky; čte a vrací náhodné stránky z disku. Poskytuje tedy náhodnost na úrovni stránky místo na úrovni n-tice. Proto je obtížné přesně kontrolovat počet načtených řádků. Pokud nejsou vybrány žádné stránky, nemusíme získat žádný výsledek. Vzorkování na úrovni stránky je také náchylné ke shlukování.

TABLESAMPLE SYNTAX je stejný pro BERNOULLI a SYSTEM metody, určíme procento, jako jsme to udělali v BERNOULLI metoda.

Rychlé připomenutí: SYNTAXE

SELECT select_expression
FROM table_name
TABLESAMPLE sampling_method ( argument [, ...] ) [ REPEATABLE ( seed ) ]
...

Abychom mohli načíst přibližně 100 řádků, musíme požádat o 100 * 100 / 1 000 000 =0,01 % řádků tabulky. Naše procento tedy bude 0,01.

Než začnete dotazovat, připomeňme si, jak je REPEATABLE funguje. V zásadě vybereme parametr seed a dostaneme stejné výsledky pokaždé, když použijeme stejný seed v předchozím dotazu. Pokud poskytneme jiný zdroj, dotaz vytvoří zcela odlišnou sadu výsledků.

Zkusme se podívat na výsledky pomocí dotazování.

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)

Dostaneme 136 řádků, protože toto číslo můžete uvažovat podle toho, kolik řádků je uloženo na jedné stránce.

Nyní zkusme spustit stejný dotaz se stejným počátečním číslem:

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)

Díky REPEATABLE získáme stejnou sadu výsledků volba. Nyní můžeme porovnat TABLESAMPLE SYSTEM metodou s náhodným sloupcem pomocí EXPLAIN ANALYZE výstup.

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 využívá skenování vzorků a je extrémně rychlá. Jedinou nevýhodou této metody je, že nemůžeme zaručit, že poskytnuté procento povede k očekávanému počtu řádků.

Závěr

V tomto blogovém příspěvku jsme porovnali standardní TABLESAMPLE (SYSTEM , BERNOULLI ) a tsm_system_rows modul se staršími náhodnými metodami.

Zde si můžete rychle prohlédnout zjištění:

  • ORDER BY random() je pomalý kvůli řazení
  • random() <= 0.1 je podobný BERNOULLI metoda
  • Přidání sloupce s náhodnou hodnotou vyžaduje počáteční práci a může vést k problémům s výkonem
  • SYSTEM je rychlý, ale je těžké kontrolovat počet řádků
  • tsm_system_rows je rychlý a dokáže ovládat počet řádků

Výsledkem je tsm_system_rows překonává jakoukoli jinou metodu pro výběr pouze několika náhodných řádků.

Ale skutečným vítězem je rozhodně TABLESAMPLE sám. Díky své rozšiřitelnosti mohou být vlastní rozšíření (tj. tsm_system_rows , tsm_system_time ) lze vyvinout pomocí TABLESAMPLE funkce metody.

Poznámka pro vývojáře: Více informací o tom, jak napsat vlastní metody vzorkování, lze nalézt v kapitole Writing A Table Sampling Method v dokumentaci PostgreSQL.

Poznámka do budoucna: O projektu AXLE a rozšíření tsm_system_time budeme diskutovat v našem dalším TABLESAMPLE blogový příspěvek.


  1. ORA-01036:neplatný název/číslo proměnné při spuštění dotazu přes C#

  2. Vlastní ORDER BY Vysvětlení

  3. Oracle PL/SQL:jak získat trasování zásobníku, název balíčku a název procedury

  4. SQL Server:Filtr výstup sp_who2