sql >> Database >  >> RDS >> PostgreSQL

Tablesample en andere methoden om willekeurige tuples te krijgen

TABLESAMPLE van PostgreSQL biedt nog een paar voordelen in vergelijking met andere traditionele manieren om willekeurige tuples te krijgen.

TABLESAMPLE is een SQL SELECT-clausule en biedt twee bemonsteringsmethoden:SYSTEM en BERNOULLI . Met behulp van TABLESAMPLE we kunnen gemakkelijk willekeurige rijen uit een tabel halen. Voor meer informatie over TABLESAMPLE kun je de vorige blogpost bekijken .

In deze blogpost zullen we het hebben over alternatieve manieren om willekeurige rijen te krijgen. Hoe mensen willekeurige rijen selecteerden vóór TABLESAMPLE , wat zijn de voor- en nadelen van de andere methoden en wat hebben we gewonnen met TABLESAMPLE ?

Er zijn geweldige blogposts over het selecteren van willekeurige rijen, je kunt de volgende blogposts lezen om een ​​goed begrip van dit onderwerp te krijgen.

Mijn gedachten over het krijgen van willekeurige rij

Willekeurige rijen uit een databasetabel halen

random_agg()

Laten we de traditionele manieren om willekeurige rijen uit een tabel te halen, vergelijken met de nieuwe manieren van TABLESAMPLE.

Voor de TABLESAMPLE clausule, waren er 3 veelgebruikte methoden voor het willekeurig selecteren van rijen uit een tabel.

1- Order by random()

Voor testdoeleinden moeten we een tabel maken en er wat gegevens in plaatsen.

Laten we een ts_test-tabel maken en er 1M rijen in invoegen:

CREATE TABLE ts_test (
  id SERIAL PRIMARY KEY,
  title TEXT
);

INSERT INTO ts_test (title)
SELECT
    'Record #' || i
FROM
    generate_series(1, 1000000) i;

Gezien de volgende SQL-instructie voor het selecteren van 10 willekeurige rijen:

SELECT * FROM ts_test ORDER BY random() LIMIT 10;

Zorgt ervoor dat PostgreSQL een volledige tabelscan uitvoert en ook bestelt. Daarom heeft deze methode om prestatieredenen niet de voorkeur voor tabellen met een groot aantal rijen.

Laten we eens kijken naar EXPLAIN ANALYZE uitvoer van deze vraag hierboven:

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)

Als EXPLAIN ANALYZE wijst erop dat het selecteren van 10 van de 1 miljoen rijen bijna 2 seconden duurde. De query gebruikte ook de uitvoer van random() als de sorteersleutel om resultaten te bestellen. Sorteren lijkt hier de meest tijdrovende taak te zijn. Laten we dit uitvoeren met een scenario met behulp van TABLESAMPLE .

In PostgreSQL 9.5 kunnen we, om het exacte aantal rijen willekeurig te krijgen, de SYSTEM_ROWS-steekproefmethode gebruiken. Geleverd door de tsm_system_rows contrib-module, stelt het ons in staat om te specificeren hoeveel rijen moeten worden geretourneerd door middel van steekproeven. Normaal gesproken kan alleen het gevraagde percentage worden opgegeven met TABLESAMPLE SYSTEM en BERNOULLI methoden.

Eerst moeten we tsm_system_rows create maken extensie voor het gebruik van deze methode aangezien zowel TABLESAMPLE SYSTEM en TABLESAMPLE BERNOULLI methoden garanderen niet dat het opgegeven percentage zal resulteren in het verwachte aantal rijen. Controleer het vorige TABLESAMPLE p om te onthouden waarom ze zo werken.

Laten we beginnen met het maken van tsm_system_rows extensie:

random=# CREATE EXTENSION tsm_system_rows;
CREATE EXTENSION

Laten we nu vergelijken "ORDER BY random()EXPLAIN ANALYZE uitvoer met de EXPLAIN ANALYZE uitvoer van tsm_system_rows query die 10 willekeurige rijen retourneert uit een rijtabel van 1 miljoen.

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)

De hele query duurde 0,159 ms. Deze steekproefmethode is extreem snel in vergelijking met de "ORDER BY random() ”-methode die 1956,9 ms duurde.

2- Vergelijk met random()

Met de volgende SQL kunnen we willekeurige rijen ophalen met een waarschijnlijkheid van 10%

SELECT * FROM ts_test WHERE random() <= 0.1;

Deze methode is sneller dan willekeurig sorteren omdat geselecteerde rijen niet worden gesorteerd. Het geeft het geschatte percentage rijen uit de tabel terug, net als BERNOULLI of SYSTEM TABLESAMPLE methoden.

Laten we de EXPLAIN ANALYZE eens bekijken uitvoer van random() vraag hierboven:

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)

De query duurde 367,5 ms. Veel beter dan ORDER BY random() . Maar het is moeilijker om het exacte aantal rijen te controleren. Laten we "random() . vergelijken ” EXPLAIN ANALYZE uitvoer met de EXPLAIN ANALYZE uitvoer van TABLESAMPLE BERNOULLI query die ongeveer 10% van de willekeurige rijen retourneert uit de tabel met 1 miljoen rijen.

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)

We hebben 10 als parameter gegeven aan BERNOULLI omdat we 10% van alle records nodig hebben.

Hier kunnen we zien dat de BERNOULLI methode duurde 239.289 ms om uit te voeren. Deze twee methoden lijken erg op elkaar in hoe ze werken, BERNOULLI is iets sneller omdat de willekeurige selectie allemaal op een lager niveau wordt gedaan. Een voordeel van het gebruik van BERNOULLI vergeleken met WHERE random() <= 0.1 is de REPEATABLE clausule die we beschreven in vorige TABLESAMPLE bericht.

3- Extra kolom met een willekeurige waarde

Deze methode stelt voor om een ​​nieuwe kolom toe te voegen met willekeurig toegewezen waarden, er een index aan toe te voegen, sortering op die kolom uit te voeren en optioneel hun waarden periodiek bij te werken om de distributie willekeurig te maken.

Deze strategie maakt meestal herhaalbare willekeurige steekproeven mogelijk. Het werkt veel sneller dan de eerste methode, maar het kost veel moeite om het voor de eerste keer in te stellen en resulteert in prestatiekosten bij het invoegen, bijwerken en verwijderen.

Laten we deze methode toepassen op onze ts_test tafel.

Eerst zullen we een nieuwe kolom toevoegen met de naam randomcolumn met het type dubbele precisie omdat PostgreSQL's random() functie retourneert een getal met dubbele precisie.

random=# ALTER TABLE ts_test ADD COLUMN randomcolumn DOUBLE PRECISION;
ALTER TABLE

Daarna updaten we de nieuwe kolom met behulp van random() functie.

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

Deze methode heeft initiële kosten voor het maken van een nieuwe kolom en het vullen van die nieuwe kolom met willekeurige (0,0-1,0) waarden, maar het zijn eenmalige kosten. In dit voorbeeld duurde het voor rijen van 1 miljoen bijna 8,5 seconden.

Laten we proberen te observeren of onze resultaten reproduceerbaar zijn door 100 rijen te doorzoeken met onze nieuwe methode:

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)

Wanneer we de bovenstaande query uitvoeren, krijgen we meestal dezelfde resultatenset, maar dit is niet gegarandeerd omdat we random() hebben gebruikt functie voor het invullen van randomcolumn waarden en in dit geval kunnen meerdere kolommen dezelfde waarde hebben. Om er zeker van te zijn dat we dezelfde resultaten krijgen voor elke keer dat het wordt uitgevoerd, moeten we onze zoekopdracht verbeteren door de ID-kolom toe te voegen aan ORDER BY clausule, op deze manier zorgen we ervoor dat ORDER BY clausule specificeert een unieke set rijen, omdat de id-kolom een ​​primaire sleutelindex bevat.

Laten we nu de verbeterde query hieronder uitvoeren:

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 zijn we er zeker van dat we een reproduceerbare willekeurige steekproef kunnen krijgen door deze methode te gebruiken.

Het is tijd om EXPLAIN ANALYZE . te zien resultaten van onze laatste vraag:

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)

Om deze methode te vergelijken met TABLESAMPLE methoden, laten we SYSTEM kiezen methode met REPEATABLE optie, aangezien deze methode ons reproduceerbare resultaten geeft.

TABLESAMPLE SYSTEM methode blokkeert in feite bemonstering op paginaniveau; het leest en retourneert willekeurige pagina's van schijf. Het biedt dus willekeur op paginaniveau in plaats van op tupelniveau. Daarom is het moeilijk om het aantal opgehaalde rijen precies te controleren. Als er geen pagina's zijn gekozen, krijgen we mogelijk geen resultaat. De bemonstering op paginaniveau is ook gevoelig voor clustering.

TABLESAMPLE SYNTAX is hetzelfde voor BERNOULLI en SYSTEM methoden, zullen we het percentage specificeren zoals we deden in BERNOULLI methode.

Snelle herinnering: SYNTAX

SELECT select_expression
FROM table_name
TABLESAMPLE sampling_method ( argument [, ...] ) [ REPEATABLE ( seed ) ]
...

Om ongeveer 100 rijen op te halen, moeten we 100 * 100 / 1 000 000 =0,01% van de rijen van de tabel opvragen. Ons percentage zal dus 0,01 zijn.

Laten we, voordat we beginnen met zoeken, onthouden hoe REPEATABLE werken. In principe kiezen we een seed-parameter en krijgen we dezelfde resultaten voor elke keer dat we dezelfde seed gebruiken met de vorige zoekopdracht. Als we een andere seed leveren, zal de query een heel andere resultatenset opleveren.

Laten we proberen de resultaten te bekijken met zoekopdrachten.

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)

We krijgen 136 rijen, aangezien je kunt bedenken dat dit aantal afhangt van het aantal rijen dat op één pagina is opgeslagen.

Laten we nu proberen dezelfde query uit te voeren met hetzelfde seed-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)

We krijgen dezelfde resultatenset dankzij REPEATABLE optie. Nu kunnen we TABLESAMPLE SYSTEM vergelijken methode met willekeurige kolommethode door te kijken naar de EXPLAIN ANALYZE uitvoer.

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 methode maakt gebruik van voorbeeldscan en is extreem snel. Het enige nadeel van deze methode is dat we niet kunnen garanderen dat het opgegeven percentage zal resulteren in het verwachte aantal rijen.

Conclusie

In deze blogpost vergeleken we standaard TABLESAMPLE (SYSTEM , BERNOULLI ) en de tsm_system_rows module met de oudere willekeurige methoden.

Hier kunt u de bevindingen snel bekijken:

  • ORDER BY random() is traag vanwege het sorteren
  • random() <= 0.1 lijkt op BERNOULLI methode
  • Het toevoegen van een kolom met willekeurige waarde vereist eerst werk en kan prestatieproblemen veroorzaken
  • SYSTEM is snel, maar het is moeilijk om het aantal rijen te controleren
  • tsm_system_rows is snel en kan het aantal rijen bepalen

Als resultaat tsm_system_rows verslaat elke andere methode om slechts enkele willekeurige rijen te selecteren.

Maar de echte winnaar is zeker TABLESAMPLE zelf. Dankzij de uitbreidbaarheid kunnen aangepaste extensies (d.w.z. tsm_system_rows , tsm_system_time ) kan worden ontwikkeld met behulp van TABLESAMPLE methode functies.

Opmerking voor ontwikkelaars: Meer informatie over het schrijven van aangepaste bemonsteringsmethoden vindt u in het hoofdstuk Een tabelbemonsteringsmethode schrijven van de PostgreSQL-documentatie.

Opmerking voor de toekomst: We bespreken het AXLE-project en de tsm_system_time-extensie in onze volgende TABLESAMPLE blogbericht.


  1. SQLite-queryresultaten opmaken als een HTML-tabel

  2. Hoe verouderde oracle.sql.ArrayDescriptor, oracle.sql.STRUCT en oracle.sql.StructDescriptor te repareren

  3. INSERT INTO gebruiken vanuit SQL Server om Salesforce-gegevens te wijzigen

  4. Exporteer SQLite-database naar een CSV-bestand