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.