PostgreSQL:s TABLESAMPLE ger några fler fördelar jämfört med andra traditionella sätt att få slumpmässiga tupler.
TABLESAMPLE
är en SQL SELECT-sats och den tillhandahåller två samplingsmetoder som är SYSTEM
och BERNOULLI
. Med hjälp av TABLESAMPLE
vi kan enkelt hämta slumpmässiga rader från en tabell. För ytterligare läsning om TABLESAMPLE kan du kolla föregående blogginlägg .
I det här blogginlägget kommer vi att prata om alternativa sätt att få slumpmässiga rader. Hur folk valde slumpmässiga rader före TABLESAMPLE
, vilka är för- och nackdelarna med de andra metoderna och vad vi fick med TABLESAMPLE
?
Det finns fantastiska blogginlägg om att välja slumpmässiga rader, du kan börja läsa följande blogginlägg för att få en djup förståelse av detta ämne.
Mina tankar om att få slumpmässig rad
Få slumpmässiga rader från en databastabell
random_agg()
Låt oss jämföra de traditionella sätten att få slumpmässiga rader från en tabell med de nya sätten som TABLESAMPLE erbjuder.
Före TABLESAMPLE
sats, det fanns 3 vanliga metoder för att slumpmässigt välja rader från en tabell.
1- Order by random()
För teständamål måste vi skapa en tabell och lägga in lite data i den.
Låt oss skapa ts_test-tabellen och infoga 1M rader i den:
CREATE TABLE ts_test (
id SERIAL PRIMARY KEY,
title TEXT
);
INSERT INTO ts_test (title)
SELECT
'Record #' || i
FROM
generate_series(1, 1000000) i;
Överväger följande SQL-sats för att välja 10 slumpmässiga rader:
SELECT * FROM ts_test ORDER BY random() LIMIT 10;
Får PostgreSQL att utföra en fullständig tabellskanning och även beställa. Därför är den här metoden inte att föredra för tabeller med ett stort antal rader på grund av prestandaskäl.
Låt oss titta på EXPLAIN ANALYZE
utdata från denna fråga ovan:
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)
Som EXPLAIN ANALYZE
påpekar, att välja 10 av 1M rader tog nästan 2 sekunder. Frågan använde också utdata från random()
som sorteringsnyckel för att beställa resultat. Sortering verkar vara den mest tidskrävande uppgiften här. Låt oss köra detta med scenariot med TABLESAMPLE
.
I PostgreSQL 9.5, för att få exakt antal rader slumpmässigt, kan vi använda SYSTEM_ROWS samplingsmetoden. Tillhandahålls av tsm_system_rows
contrib-modulen låter den oss ange hur många rader som ska returneras genom sampling. Normalt sett kunde bara den begärda procentsatsen anges med TABLESAMPLE SYSTEM
och BERNOULLI
metoder.
Först bör vi skapa tsm_system_rows
förlängning för att använda denna metod eftersom både TABLESAMPLE SYSTEM
och TABLESAMPLE BERNOULLI
metoder garanterar inte att den angivna procentandelen kommer att resultera i det förväntade antalet rader. Kontrollera föregående TABLESAMPLE p ost för att komma ihåg varför de fungerar så.
Låt oss börja med att skapa tsm_system_rows
tillägg:
random=# CREATE EXTENSION tsm_system_rows;
CREATE EXTENSION
Låt oss nu jämföra "ORDER BY random()
” EXPLAIN ANALYZE
mata ut med EXPLAIN ANALYZE
utdata från tsm_system_rows
fråga som returnerar 10 slumpmässiga rader av en 1M radtabell.
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)
Hela frågan tog 0,159 ms. Denna samplingsmetod är extremt snabb att jämföra med "ORDER BY random()
” metod som tog 1956,9 ms.
2- Jämför med random()
Följande SQL tillåter oss att hämta slumpmässiga rader med 10 % sannolikhet
SELECT * FROM ts_test WHERE random() <= 0.1;
Den här metoden är snabbare än att beställa slumpmässigt eftersom den inte sorterar valda rader. Det kommer att returnera ungefärlig procentandel av rader från tabellen precis som BERNOULLI
eller SYSTEM
TABLESAMPLE
metoder.
Låt oss kontrollera EXPLAIN ANALYZE
utdata av random()
fråga ovan:
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)
Frågan tog 367,5 ms. Mycket bättre än ORDER BY random()
. Men det är svårare att kontrollera det exakta antalet rader. Låt oss jämföra "random()
” EXPLAIN ANALYZE
mata ut med EXPLAIN ANALYZE
utdata från TABLESAMPLE BERNOULLI
fråga som returnerar cirka 10 % av slumpmässiga rader från tabellen med 1 M rader.
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)
Vi gav 10 som parameter till BERNOULLI
eftersom vi behöver 10 % av alla poster.
Här kan vi se att BERNOULLI
metoden tog 239,289 ms att köra. Dessa två metoder är ganska lika i hur de fungerar, BERNOULLI
är något snabbare eftersom det slumpmässiga urvalet görs på lägre nivå. En fördel med att använda BERNOULLI
jämfört med WHERE random() <= 0.1
är REPEATABLE
sats som vi beskrev i tidigare TABLESAMPLE
inlägg.
3- Extra kolumn med ett slumpmässigt värde
Den här metoden föreslår att man lägger till en ny kolumn med slumpmässigt tilldelade värden, lägger till ett index till den, utför sortering efter den kolumnen och eventuellt uppdaterar deras värden med jämna mellanrum för att slumpmässigt fördela fördelningen.
Denna strategi tillåter mestadels repeterbart slumpmässigt urval. Det fungerar mycket snabbare än den första metoden, men det tar en ansträngning att ställa in för första gången och resulterar i en prestandakostnad för att infoga, uppdatera och ta bort operationer.
Låt oss tillämpa den här metoden på vår ts_test
bord.
Först lägger vi till en ny kolumn som heter randomcolumn
med dubbel precision eftersom PostgreSQL:s random()
funktion returnerar ett tal med dubbel precision.
random=# ALTER TABLE ts_test ADD COLUMN randomcolumn DOUBLE PRECISION;
ALTER TABLE
Sedan uppdaterar vi den nya kolumnen med random()
funktion.
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
Den här metoden har en initial kostnad för att skapa en ny kolumn och fylla den nya kolumnen med slumpmässiga (0,0-1,0) värden, men det är en engångskostnad. I det här exemplet, för 1M rader, tog det nästan 8,5 sekunder.
Låt oss försöka se om våra resultat är reproducerbara genom att fråga 100 rader med vår nya 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)
När vi kör frågan ovan får vi för det mesta samma resultatuppsättning men detta är inte garanterat eftersom vi använde random()
funktion för att fylla i randomcolumn
värden och i det här fallet kan mer än en kolumn ha samma värde. För att vara säkra på att vi får samma resultat för varje gång den körs bör vi förbättra vår fråga genom att lägga till ID-kolumnen i ORDER BY
klausul, på detta sätt säkerställer vi att ORDER BY
sats anger en unik uppsättning rader, eftersom id-kolumnen har ett primärnyckelindex.
Låt oss nu köra den förbättrade frågan nedan:
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)
Nu är vi säkra på att vi kan få ett reproducerbart slumpmässigt urval genom att använda den här metoden.
Det är dags att se EXPLAIN ANALYZE
resultat av vår slutliga fråga:
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)
För att jämföra denna metod med TABLESAMPLE
metoder, låt oss välja SYSTEM
metod med REPEATABLE
alternativet, eftersom den här metoden ger oss reproducerbara resultat.
TABLESAMPLE
SYSTEM
metoden gör i princip sampling på block-/sidnivå; den läser och returnerar slumpmässiga sidor från disken. Det ger alltså slumpmässighet på sidnivå istället för tuppelnivå. Det är därför det är svårt att kontrollera antalet hämtade rader exakt. Om det inte finns några utvalda sidor kanske vi inte får något resultat. Samplingen på sidnivå är också benägen att ha klustringseffekter.
TABLESAMPLE
SYNTAX är samma för BERNOULLI
och SYSTEM
metoder, anger vi procentandelen som vi gjorde i BERNOULLI
metod.
Snabb påminnelse: SYNTAX
SELECT select_expression
FROM table_name
TABLESAMPLE sampling_method ( argument [, ...] ) [ REPEATABLE ( seed ) ]
...
För att kunna hämta cirka 100 rader måste vi begära 100 * 100 / 1 000 000 =0,01 % av tabellens rader. Så vår procentandel blir 0,01.
Innan vi börjar fråga, låt oss komma ihåg hur REPEATABLE
Arbetar. I grund och botten kommer vi att välja en fröparameter och vi får samma resultat för varje gång vi använder samma frö med den föregående frågan. Om vi tillhandahåller ett annat frö kommer frågan att ge en helt annan resultatuppsättning.
Låt oss försöka se resultaten med frågor.
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)
Vi får 136 rader, eftersom du kan tänka dig att detta antal beror på hur många rader som finns lagrade på en enda sida.
Låt oss nu försöka köra samma fråga med samma frönummer:
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)
Vi får samma resultatuppsättning tack vare REPEATABLE
alternativ. Nu kan vi jämföra TABLESAMPLE SYSTEM
metod med slumpmässig kolumnmetod genom att titta på EXPLAIN ANALYZE
utdata.
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
Metoden använder provskanning och den är extremt snabb. Det enda bakslaget med denna metod är att vi inte kan garantera att den angivna procentandelen kommer att resultera i det förväntade antalet rader.
Slutsats
I det här blogginlägget jämförde vi standard TABLESAMPLE
(SYSTEM
, BERNOULLI
) och tsm_system_rows
modul med de äldre slumpmässiga metoderna.
Här kan du snabbt granska resultaten:
ORDER BY random()
går långsamt på grund av sorteringrandom() <= 0.1
liknarBERNOULLI
metod- Att lägga till kolumn med slumpmässigt värde kräver inledande arbete och kan leda till prestandaproblem
SYSTEM
är snabb men det är svårt att kontrollera antalet radertsm_system_rows
är snabb och kan styra antalet rader
Som ett resultat tsm_system_rows
slår alla andra metoder för att välja några slumpmässiga rader.
Men den verkliga vinnaren är definitivt TABLESAMPLE
sig. Tack vare dess utökbarhet, anpassade tillägg (dvs. tsm_system_rows
, tsm_system_time
) kan utvecklas med TABLESAMPLE
metodfunktioner.
Utvecklarnotering: Mer information om hur man skriver anpassade samplingsmetoder finns i kapitlet Skriva en tabellsamplingsmetod i PostgreSQL-dokumentationen.
Meddelande till framtiden: Vi kommer att diskutera AXLE Project och tsm_system_time extension i vår nästa TABLESAMPLE
blogginlägg.