sql >> Databasteknik >  >> RDS >> PostgreSQL

Tabellexempel och andra metoder för att få slumpmässiga tuplar

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 sortering
  • random() <= 0.1 liknar BERNOULLI 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 rader
  • tsm_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.


  1. Varför kan jag inte ange detta datum i en tabell med SQL?

  2. Få den första måndagen på ett år i SQLite

  3. Försöker distribuera Oracle-ADF-applikationen till Tomcat 7

  4. INSERT IGNORE vs INSERT ... PÅ DUBLIKATNYCKELUPPDATERING