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 sorterenrandom() <= 0.1
lijkt opBERNOULLI
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 controlerentsm_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.