TABLESAMPLE di PostgreSQL offre alcuni vantaggi in più rispetto ad altri metodi tradizionali per ottenere tuple casuali.
TABLESAMPLE
è una clausola SQL SELECT e fornisce due metodi di campionamento che sono SYSTEM
e BERNOULLI
. Con l'aiuto di TABLESAMPLE
possiamo facilmente recuperare righe casuali da una tabella. Per ulteriori informazioni su TABLESAMPLE puoi controllare il post del blog precedente .
In questo post del blog parleremo di modi alternativi per ottenere righe casuali. Come le persone selezionavano righe casuali prima di TABLESAMPLE
, quali sono i pro ei contro degli altri metodi e cosa abbiamo guadagnato con TABLESAMPLE
?
Ci sono fantastici post sul blog sulla selezione di righe casuali, puoi iniziare a leggere i seguenti post sul blog per acquisire una profonda comprensione di questo argomento.
I miei pensieri su come ottenere una riga casuale
Ottenere righe casuali da una tabella del database
casuale_agg()
Confrontiamo i metodi tradizionali per ottenere righe casuali da una tabella con i nuovi metodi forniti da TABLESAMPLE.
Prima del TABLESAMPLE
clausola, c'erano 3 metodi comunemente usati per la selezione casuale di righe da una tabella.
1- Ordina per random()
A scopo di test, dobbiamo creare una tabella e inserire alcuni dati al suo interno.
Creiamo la tabella ts_test e inseriamo 1M di righe al suo interno:
CREATE TABLE ts_test (
id SERIAL PRIMARY KEY,
title TEXT
);
INSERT INTO ts_test (title)
SELECT
'Record #' || i
FROM
generate_series(1, 1000000) i;
Considerando la seguente istruzione SQL per selezionare 10 righe casuali:
SELECT * FROM ts_test ORDER BY random() LIMIT 10;
Fa sì che PostgreSQL esegua una scansione completa della tabella e anche l'ordinamento. Pertanto questo metodo non è preferito per le tabelle con un numero elevato di righe per motivi di prestazioni.
Esaminiamo EXPLAIN ANALYZE
output di questa query sopra:
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)
Come EXPLAIN ANALYZE
sottolinea, la selezione di 10 righe su 1 milione ha richiesto quasi 2 secondi. La query utilizzava anche l'output di random()
come chiave di ordinamento per ordinare i risultati. L'ordinamento sembra essere l'attività che richiede più tempo qui. Eseguiamolo con lo scenario usando TABLESAMPLE
.
In PostgreSQL 9.5, per ottenere il numero esatto di righe in modo casuale, possiamo usare il metodo di campionamento SYSTEM_ROWS. Fornito da tsm_system_rows
modulo contrib, ci permette di specificare quante righe devono essere restituite dal campionamento. Normalmente solo la percentuale richiesta può essere specificata con TABLESAMPLE SYSTEM
e BERNOULLI
metodi.
Per prima cosa, dobbiamo creare tsm_system_rows
estensione per l'utilizzo di questo metodo poiché entrambi TABLESAMPLE SYSTEM
e TABLESAMPLE BERNOULLI
i metodi non garantiscono che la percentuale fornita risulterà nel numero previsto di righe. Si prega di controllare il precedente TABLESAMPLE p ost per ricordare perché funzionano così.
Iniziamo creando tsm_system_rows
estensione:
random=# CREATE EXTENSION tsm_system_rows;
CREATE EXTENSION
Ora confrontiamo "ORDER BY random()
” EXPLAIN ANALYZE
output con EXPLAIN ANALYZE
output di tsm_system_rows
query che restituisce 10 righe casuali da una tabella di 1 milione di righe.
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)
L'intera query ha richiesto 0,159 ms. Questo metodo di campionamento è estremamente veloce rispetto a "ORDER BY random()
” metodo che ha richiesto 1956,9 ms.
2- Confronta con random()
Il seguente SQL ci consente di recuperare righe casuali con una probabilità del 10%
SELECT * FROM ts_test WHERE random() <= 0.1;
Questo metodo è più veloce dell'ordinamento casuale perché non ordina le righe selezionate. Restituirà una percentuale approssimativa di righe dalla tabella proprio come BERNOULLI
o SYSTEM
TABLESAMPLE
metodi.
Controlliamo il EXPLAIN ANALYZE
output di random()
query sopra:
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)
La query ha richiesto 367,5 ms. Molto meglio di ORDER BY random()
. Ma è più difficile controllare il conteggio esatto delle righe. Confrontiamo "random()
” EXPLAIN ANALYZE
output con EXPLAIN ANALYZE
output di TABLESAMPLE BERNOULLI
query che restituisce circa il 10% di righe casuali dalla tabella di 1 milione di righe.
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)
Abbiamo dato 10 come parametro a BERNOULLI
perché abbiamo bisogno del 10% di tutti i record.
Qui possiamo vedere che il BERNOULLI
il metodo ha richiesto 239,289 ms per l'esecuzione. Questi due metodi sono abbastanza simili nel modo in cui funzionano, BERNOULLI
è leggermente più veloce in quanto la selezione casuale viene eseguita a un livello inferiore. Un vantaggio nell'usare BERNOULLI
rispetto a WHERE random() <= 0.1
è il REPEATABLE
clausola che abbiamo descritto nel precedente TABLESAMPLE
posta.
3- Colonna aggiuntiva con un valore casuale
Questo metodo suggerisce di aggiungere una nuova colonna con valori assegnati casualmente, aggiungere un indice ad essa, eseguire l'ordinamento in base a quella colonna e, facoltativamente, aggiornare periodicamente i loro valori per randomizzare la distribuzione.
Questa strategia consente un campionamento casuale per lo più ripetibile. Funziona molto più velocemente del primo metodo, ma richiede uno sforzo per la configurazione per la prima volta e comporta un costo in termini di prestazioni nelle operazioni di inserimento, aggiornamento ed eliminazione.
Applichiamo questo metodo sul nostro ts_test
tabella.
Per prima cosa, aggiungeremo una nuova colonna chiamata randomcolumn
con il tipo a doppia precisione perché random()
di PostgreSQL la funzione restituisce un numero con precisione doppia.
random=# ALTER TABLE ts_test ADD COLUMN randomcolumn DOUBLE PRECISION;
ALTER TABLE
Quindi aggiorneremo la nuova colonna utilizzando random()
funzione.
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
Questo metodo ha un costo iniziale per la creazione di una nuova colonna e il popolamento di quella nuova colonna con valori casuali (0,0-1,0), ma è un costo una tantum. In questo esempio, per 1 milione di righe, ci sono voluti quasi 8,5 secondi.
Proviamo ad osservare se i nostri risultati sono riproducibili interrogando 100 righe con il nostro nuovo metodo:
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)
Quando eseguiamo la query sopra, otteniamo principalmente lo stesso set di risultati, ma questo non è garantito perché abbiamo utilizzato random()
funzione per popolare randomcolumn
valori e in questo caso più di una colonna potrebbe avere lo stesso valore. Per essere sicuri di ottenere gli stessi risultati ogni volta che viene eseguito, dovremmo migliorare la nostra query aggiungendo la colonna ID a ORDER BY
clausola, in questo modo assicuriamo che ORDER BY
La clausola specifica un insieme univoco di righe, perché la colonna id contiene l'indice della chiave primaria.
Ora eseguiamo la query migliorata di seguito:
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)
Ora siamo sicuri di poter ottenere campioni casuali riproducibili utilizzando questo metodo.
È ora di vedere EXPLAIN ANALYZE
risultati della nostra domanda finale:
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)
Per confrontare questo metodo con TABLESAMPLE
metodi, scegliamo SYSTEM
metodo con REPEATABLE
opzione, poiché questo metodo ci dà risultati riproducibili.
TABLESAMPLE
SYSTEM
il metodo fondamentalmente fa il campionamento a livello di blocco/pagina; legge e restituisce pagine casuali dal disco. Pertanto fornisce casualità a livello di pagina anziché a livello di tupla. Ecco perché è difficile controllare esattamente il conteggio delle righe recuperate. Se non ci sono pagine selezionate potremmo non ottenere alcun risultato. Anche il campionamento a livello di pagina tende all'effetto di raggruppamento.
TABLESAMPLE
LA SINTASSI è la stessa per BERNOULLI
e SYSTEM
metodi, specificheremo la percentuale come abbiamo fatto in BERNOULLI
metodo.
Promemoria rapido: SINTASSI
SELECT select_expression
FROM table_name
TABLESAMPLE sampling_method ( argument [, ...] ) [ REPEATABLE ( seed ) ]
...
Per recuperare circa 100 righe, dobbiamo richiedere 100 * 100 / 1 000 000 =0,01% delle righe della tabella. Quindi la nostra percentuale sarà 0,01.
Prima di iniziare a interrogare, ricordiamo come REPEATABLE
lavori. Fondamentalmente sceglieremo un parametro seed e otterremo gli stessi risultati ogni volta che utilizzeremo lo stesso seme con la query precedente. Se forniamo un seme diverso, la query produrrà un set di risultati abbastanza diverso.
Proviamo a vedere i risultati con le query.
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)
Otteniamo 136 righe, poiché puoi considerare che questo numero dipende da quante righe sono memorizzate in una singola pagina.
Ora proviamo a eseguire la stessa query con lo stesso numero di seed:
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)
Otteniamo lo stesso set di risultati grazie a REPEATABLE
opzione. Ora possiamo confrontare TABLESAMPLE SYSTEM
metodo con metodo a colonna casuale guardando EXPLAIN ANALYZE
uscita.
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
il metodo utilizza la scansione del campione ed è estremamente veloce. L'unico aspetto negativo di questo metodo è che non possiamo garantire che la percentuale fornita comporterà il numero previsto di righe.
Conclusione
In questo post del blog abbiamo confrontato TABLESAMPLE
standard (SYSTEM
, BERNOULLI
) e il tsm_system_rows
modulo con i metodi casuali precedenti.
Qui puoi rivedere rapidamente i risultati:
ORDER BY random()
è lento a causa dell'ordinamentorandom() <= 0.1
è simile aBERNOULLI
metodo- L'aggiunta di una colonna con un valore casuale richiede un lavoro iniziale e potrebbe causare problemi di prestazioni
SYSTEM
è veloce ma è difficile controllare il numero di righetsm_system_rows
è veloce e può controllare il numero delle righe
Di conseguenza tsm_system_rows
batte qualsiasi altro metodo per selezionare solo poche righe casuali.
Ma il vero vincitore è sicuramente TABLESAMPLE
si. Grazie alla sua estensibilità, estensioni personalizzate (es. tsm_system_rows
, tsm_system_time
) può essere sviluppato utilizzando TABLESAMPLE
funzioni del metodo.
Nota per gli sviluppatori: Maggiori informazioni su come scrivere metodi di campionamento personalizzati sono disponibili nel capitolo Scrittura di un metodo di campionamento su tabelle della documentazione di PostgreSQL.
Nota per il futuro: Discuteremo del progetto AXLE e dell'estensione tsm_system_time nel nostro prossimo TABLESAMPLE
post del blog.