sql >> Database teknologi >  >> RDS >> PostgreSQL

Tabelprøve og andre metoder til at få tilfældige tuples

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 sortering
  • random() <= 0.1 ligner BERNOULLI 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ækker
  • tsm_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.


  1. hvordan man emulerer insert ignore og på duplikatnøgleopdatering (sql merge) med postgresql?

  2. SQL Developer 4.0 udgivet

  3. Liste over datatyper i SQL Server 2017

  4. Hvordan bruger (installerer) dblink i PostgreSQL?