PostgreSQL's TABLESAMPLE giver et par flere fordele sammenlignet med andre traditionelle måder at få tilfældige tupler på.
TABLESAMPLE
er en SQL SELECT-sætning, og den giver to samplingmetoder, som er SYSTEM
og BERNOULLI
. Ved hjælp af TABLESAMPLE
vi kan nemt hente tilfældige rækker fra en tabel. For yderligere læsning om TABLESAMPLE kan du tjekke det forrige blogindlæg .
I dette blogindlæg vil vi tale om alternative måder at få tilfældige rækker på. Hvordan folk valgte tilfældige rækker før TABLESAMPLE
, hvad er fordele og ulemper ved de andre metoder, og hvad vi opnåede med TABLESAMPLE
?
Der er fantastiske blogindlæg om at vælge tilfældige rækker. Du kan begynde at læse følgende blogindlæg for at få en dyb forståelse af dette emne.
Mine tanker om at få tilfældig række
Hent tilfældige rækker fra en databasetabel
random_agg()
Lad os sammenligne de traditionelle måder at få tilfældige rækker fra en tabel på med de nye måder, som TABLESAMPLE tilbyder.
Før TABLESAMPLE
klausul, var der 3 almindeligt anvendte metoder til tilfældigt at vælge rækker fra en tabel.
1- Sorter efter random()
Til testformål skal vi oprette en tabel og lægge nogle data ind i den.
Lad os oprette ts_test-tabel og indsætte 1M rækker 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;
Overvejer følgende SQL-sætning til at vælge 10 tilfældige rækker:
SELECT * FROM ts_test ORDER BY random() LIMIT 10;
Får PostgreSQL til at udføre en fuld bordscanning og også bestille. Derfor foretrækkes denne metode ikke til tabeller med et stort antal rækker på grund af ydeevneårsager.
Lad os se på EXPLAIN ANALYZE
output af denne forespørgsel ovenfor:
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åpeger, at det tog næsten 2 sekunder at vælge 10 ud af 1M rækker. Forespørgslen brugte også outputtet af random()
som sorteringsnøgle til at bestille resultater. Sortering ser ud til at være den mest tidskrævende opgave her. Lad os udføre dette med scenariet ved hjælp af TABLESAMPLE
.
I PostgreSQL 9.5, for at få det nøjagtige antal rækker tilfældigt, kan vi bruge SYSTEM_ROWS prøveudtagningsmetoden. Leveret af tsm_system_rows
contrib-modul, giver det os mulighed for at specificere, hvor mange rækker der skal returneres ved stikprøvetagning. Normalt kunne kun den anmodede procentdel angives med TABLESAMPLE SYSTEM
og BERNOULLI
metoder.
Først bør vi oprette tsm_system_rows
udvidelse for at bruge denne metode, da både TABLESAMPLE SYSTEM
og TABLESAMPLE BERNOULLI
metoderne garanterer ikke, at den angivne procentdel vil resultere i det forventede antal rækker. Tjek venligst det forrige TABLESAMPLE p ost for at huske, hvorfor de fungerer sådan.
Lad os starte med at oprette tsm_system_rows
udvidelse:
random=# CREATE EXTENSION tsm_system_rows;
CREATE EXTENSION
Lad os nu sammenligne "ORDER BY random()
” EXPLAIN ANALYZE
output med EXPLAIN ANALYZE
output af tsm_system_rows
forespørgsel, som returnerer 10 tilfældige rækker ud af en 1M rækketabel.
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)
Hele forespørgslen tog 0,159 ms. Denne prøveudtagningsmetode er ekstremt hurtig sammenlignet med "ORDER BY random()
” metode, der tog 1956,9 ms.
2- Sammenlign med random()
Følgende SQL giver os mulighed for at hente tilfældige rækker med 10 % sandsynlighed
SELECT * FROM ts_test WHERE random() <= 0.1;
Denne metode er hurtigere end at bestille tilfældigt, fordi den ikke sorterer udvalgte rækker. Det vil returnere omtrentlig procentdel af rækker fra tabellen ligesom BERNOULLI
eller SYSTEM
TABLESAMPLE
metoder.
Lad os tjekke EXPLAIN ANALYZE
output af random()
forespørgsel ovenfor:
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)
Forespørgslen tog 367,5 ms. Meget bedre end ORDER BY random()
. Men det er sværere at kontrollere det nøjagtige antal rækker. Lad os sammenligne "random()
” EXPLAIN ANALYZE
output med EXPLAIN ANALYZE
output af TABLESAMPLE BERNOULLI
forespørgsel, som returnerer ca. 10 % af tilfældige rækker ud af tabellen med 1 mio. rækker.
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 til BERNOULLI
fordi vi har brug for 10 % af alle optegnelser.
Her kan vi se, at BERNOULLI
metode tog 239.289 ms at udføre. Disse to metoder er ret ens i hvordan de virker, BERNOULLI
er lidt hurtigere, da det tilfældige valg alt sammen udføres på et lavere niveau. En fordel ved at bruge BERNOULLI
sammenlignet med WHERE random() <= 0.1
er den REPEATABLE
klausul, som vi beskrev i tidligere TABLESAMPLE
indlæg.
3- Ekstra kolonne med en tilfældig værdi
Denne metode foreslår at tilføje en ny kolonne med tilfældigt tildelte værdier, tilføje et indeks til den, udføre sortering efter den kolonne og eventuelt opdatere deres værdier med jævne mellemrum for at randomisere fordelingen.
Denne strategi tillader for det meste gentagelig tilfældig stikprøve. Det virker meget hurtigere end den første metode, men det kræver en indsats at konfigurere for første gang og resulterer i en ydelsesomkostning ved indsættelse, opdatering og sletning.
Lad os anvende denne metode på vores ts_test
tabel.
Først vil vi tilføje en ny kolonne kaldet randomcolumn
med dobbeltpræcisionstypen, fordi PostgreSQL's random()
funktion returnerer et tal med dobbelt præcision.
random=# ALTER TABLE ts_test ADD COLUMN randomcolumn DOUBLE PRECISION;
ALTER TABLE
Derefter opdaterer vi den nye kolonne ved hjælp af 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
Denne metode har en startomkostning for at oprette en ny kolonne og udfylde den nye kolonne med tilfældige (0,0-1,0) værdier, men det er en engangsomkostning. I dette eksempel, for 1M rækker, tog det næsten 8,5 sekunder.
Lad os prøve at observere, om vores resultater er reproducerbare ved at forespørge 100 rækker med vores nye metode:
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 udfører forespørgslen ovenfor, får vi for det meste det samme resultatsæt, men dette er ikke garanteret, fordi vi brugte random()
funktion til at udfylde randomcolumn
værdier, og i dette tilfælde kan mere end én kolonne have den samme værdi. For at være sikre på, at vi får de samme resultater, hver gang den kører, bør vi forbedre vores forespørgsel ved at tilføje ID-kolonnen til ORDER BY
klausul, på denne måde sikrer vi, at ORDER BY
klausul angiver et unikt sæt rækker, fordi id-kolonnen har et primært nøgleindeks.
Lad os nu køre den forbedrede forespørgsel nedenfor:
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 er vi sikre på, at vi kan få reproducerbar tilfældig prøve ved at bruge denne metode.
Det er tid til at se EXPLAIN ANALYZE
resultater af vores sidste forespørgsel:
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)
For at sammenligne denne metode med TABLESAMPLE
metoder, lad os vælge SYSTEM
metode med REPEATABLE
mulighed, da denne metode giver os reproducerbare resultater.
TABLESAMPLE
SYSTEM
metoden udfører dybest set prøveudtagning på blok-/sideniveau; den læser og returnerer tilfældige sider fra disken. Det giver således tilfældighed på sideniveau i stedet for tupelniveau. Det er derfor, det er svært at kontrollere de hentede rækker nøjagtigt. Hvis der ikke er udvalgt sider, får vi muligvis ikke noget resultat. Prøveudtagningen på sideniveau er også tilbøjelig til klyngeeffekt.
TABLESAMPLE
SYNTAX er den samme for BERNOULLI
og SYSTEM
metoder, angiver vi procentdelen, som vi gjorde i BERNOULLI
metode.
Hurtig påmindelse: SYNTAKS
SELECT select_expression
FROM table_name
TABLESAMPLE sampling_method ( argument [, ...] ) [ REPEATABLE ( seed ) ]
...
For at hente cirka 100 rækker, skal vi anmode om 100 * 100 / 1 000 000 =0,01 % af tabellens rækker. Så vores procentdel vil være 0,01.
Inden du begynder at forespørge, lad os huske hvordan REPEATABLE
arbejder. Grundlæggende vælger vi en seed-parameter, og vi får de samme resultater for hver gang, når vi bruger det samme frø med den forrige forespørgsel. Hvis vi giver et andet seed, vil forespørgslen producere et helt andet resultatsæt.
Lad os prøve at se resultaterne med forespørgsler.
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 rækker, da du kan overveje, at dette antal afhænger af, hvor mange rækker der er gemt på en enkelt side.
Lad os nu prøve at køre den samme forespørgsel med det samme 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 det samme resultatsæt takket være REPEATABLE
mulighed. Nu kan vi sammenligne TABLESAMPLE SYSTEM
metode med tilfældig kolonnemetode ved at se EXPLAIN ANALYZE
output.
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 bruger prøvescanning, og det er ekstremt hurtigt. Det eneste bagud ved denne metode er, at vi ikke kan garantere, at den angivne procentdel vil resultere i det forventede antal rækker.
Konklusion
I dette blogindlæg sammenlignede vi standard TABLESAMPLE
(SYSTEM
, BERNOULLI
) og tsm_system_rows
modul med de ældre tilfældige metoder.
Her kan du hurtigt gennemgå resultaterne:
ORDER BY random()
er langsom på grund af sorteringrandom() <= 0.1
lignerBERNOULLI
metode- Tilføjelse af kolonne med tilfældig værdi kræver indledende arbejde og kan føre til ydeevneproblemer
SYSTEM
er hurtig, men det er svært at kontrollere antallet af rækkertsm_system_rows
er hurtig og kan styre antallet af rækker
Som et resultat tsm_system_rows
slår enhver anden metode til at vælge nogle få tilfældige rækker.
Men den rigtige vinder er helt sikkert TABLESAMPLE
sig selv. Takket være dens udvidelsesmuligheder, tilpassede udvidelser (dvs. tsm_system_rows
, tsm_system_time
) kan udvikles ved hjælp af TABLESAMPLE
metodefunktioner.
Udviklerbemærkning: Flere oplysninger om, hvordan man skriver brugerdefinerede samplingmetoder, kan findes i kapitlet Skrivning af en tabelprøvemetode i PostgreSQL-dokumentationen.
Bemærkning til fremtiden: Vi diskuterer AXLE Project og tsm_system_time extension i vores næste TABLESAMPLE
blogindlæg.