PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Exemple de table et autres méthodes pour obtenir des tuples aléatoires

TABLESAMPLE de PostgreSQL apporte quelques avantages supplémentaires par rapport aux autres méthodes traditionnelles d'obtention de tuples aléatoires.

TABLESAMPLE est une clause SQL SELECT et fournit deux méthodes d'échantillonnage qui sont SYSTEM et BERNOULLI . Avec l'aide de TABLESAMPLE nous pouvons facilement récupérer des lignes aléatoires d'une table. Pour en savoir plus sur TABLESAMPLE, vous pouvez consulter l'article de blog précédent .

Dans cet article de blog, nous parlerons d'autres moyens d'obtenir des lignes aléatoires. Comment les gens sélectionnaient des lignes aléatoires avant TABLESAMPLE , quels sont les avantages et les inconvénients des autres méthodes et ce que nous avons gagné avec TABLESAMPLE ?

Il existe des articles de blog impressionnants sur la sélection de lignes aléatoires, vous pouvez commencer à lire les articles de blog suivants pour acquérir une compréhension approfondie de ce sujet.

Mes réflexions sur l'obtention d'une ligne aléatoire

Obtenir des lignes aléatoires à partir d'une table de base de données

random_agg()

Comparons les méthodes traditionnelles d'obtention de lignes aléatoires à partir d'une table avec les nouvelles méthodes fournies par TABLESAMPLE.

Avant le TABLESAMPLE clause, il y avait 3 méthodes couramment utilisées pour sélectionner au hasard des lignes dans une table.

1- Trier par hasard()

À des fins de test, nous devons créer une table et y insérer des données.

Créons la table ts_test et insérons-y 1 million de lignes :

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

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

Considérant l'instruction SQL suivante pour sélectionner 10 lignes aléatoires :

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

Demande à PostgreSQL d'effectuer une analyse complète de la table et également de la classer. Par conséquent, cette méthode n'est pas préférée pour les tables avec un grand nombre de lignes pour des raisons de performances.

Examinons EXPLAIN ANALYZE résultat de cette requête ci-dessus :

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)

Comme EXPLAIN ANALYZE souligne, la sélection de 10 lignes sur 1 million a pris près de 2 secondes. La requête a également utilisé la sortie de random() comme clé de tri pour classer les résultats. Le tri semble être la tâche la plus chronophage ici. Exécutons ceci avec un scénario en utilisant TABLESAMPLE .

Dans PostgreSQL 9.5, pour obtenir le nombre exact de lignes de manière aléatoire, nous pouvons utiliser la méthode d'échantillonnage SYSTEM_ROWS. Fourni par tsm_system_rows module contrib, il nous permet de spécifier combien de lignes doivent être retournées par échantillonnage. Normalement, seul le pourcentage demandé peut être spécifié avec TABLESAMPLE SYSTEM et BERNOULLI méthodes.

Tout d'abord, nous devons créer tsm_system_rows extension pour utiliser cette méthode puisque TABLESAMPLE SYSTEM et TABLESAMPLE BERNOULLI ne garantissent pas que le pourcentage fourni donnera le nombre de lignes attendu. Veuillez vérifier le précédent TABLESAMPLE p ost pour se rappeler pourquoi ils fonctionnent comme ça.

Commençons par créer tsm_system_rows extension :

random=# CREATE EXTENSION tsm_system_rows;
CREATE EXTENSION

Comparons maintenant "ORDER BY random()EXPLAIN ANALYZE sortie avec EXPLAIN ANALYZE sortie de tsm_system_rows requête qui renvoie 10 lignes aléatoires sur une table de 1 million de lignes.

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'ensemble de la requête a pris 0,159 ms. Cette méthode d'échantillonnage est extrêmement rapide en comparaison avec le "ORDER BY random() ” méthode qui a pris 1956,9 ms.

2- Comparer avec random()

Le SQL suivant nous permet de récupérer des lignes aléatoires avec une probabilité de 10 %

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

Cette méthode est plus rapide que le tri aléatoire car elle ne trie pas les lignes sélectionnées. Il renverra un pourcentage approximatif de lignes de la table, tout comme BERNOULLI ou SYSTEM TABLESAMPLE méthodes.

Vérifions EXPLAIN ANALYZE sortie de random() requête ci-dessus :

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 requête a pris 367,5 ms. Bien mieux que ORDER BY random() . Mais il est plus difficile de contrôler le nombre exact de lignes. Comparons "random()EXPLAIN ANALYZE sortie avec EXPLAIN ANALYZE sortie de TABLESAMPLE BERNOULLI requête qui renvoie environ 10 % de lignes aléatoires sur la table de 1 million de lignes.

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)

Nous avons donné 10 en paramètre à BERNOULLI car nous avons besoin de 10 % de tous les enregistrements.

Ici, nous pouvons voir que le BERNOULLI La méthode a pris 239,289 ms pour s'exécuter. Ces deux méthodes sont assez similaires dans leur fonctionnement, BERNOULLI est légèrement plus rapide car la sélection aléatoire est entièrement effectuée au niveau inférieur. Un avantage d'utiliser BERNOULLI par rapport à WHERE random() <= 0.1 est le REPEATABLE clause que nous avons décrite dans le précédent TABLESAMPLE poste.

3- Colonne supplémentaire avec une valeur aléatoire

Cette méthode suggère d'ajouter une nouvelle colonne avec des valeurs attribuées de manière aléatoire, d'y ajouter un index, d'effectuer un tri selon cette colonne et éventuellement de mettre à jour périodiquement leurs valeurs pour rendre aléatoire la distribution.

Cette stratégie permet un échantillonnage aléatoire principalement reproductible. Cela fonctionne beaucoup plus rapidement que la première méthode, mais cela demande un effort de configuration pour la première fois et entraîne un coût de performance dans les opérations d'insertion, de mise à jour et de suppression.

Appliquons cette méthode sur notre ts_test tableau.

Tout d'abord, nous allons ajouter une nouvelle colonne appelée randomcolumn avec le type double précision car random() de PostgreSQL fonction renvoie un nombre en double précision.

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

Ensuite, nous mettrons à jour la nouvelle colonne à l'aide de random() fonction.

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

Cette méthode a un coût initial pour créer une nouvelle colonne et remplir cette nouvelle colonne avec des valeurs aléatoires (0,0-1,0), mais c'est un coût unique. Dans cet exemple, pour 1 million de lignes, cela a pris près de 8,5 secondes.

Essayons d'observer si nos résultats sont reproductibles en interrogeant 100 lignes avec notre nouvelle méthode :

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)

Lorsque nous exécutons la requête ci-dessus, nous obtenons principalement le même ensemble de résultats, mais cela n'est pas garanti car nous avons utilisé random() fonction pour remplir randomcolumn valeurs et dans ce cas plusieurs colonnes peuvent avoir la même valeur. Pour être sûr que nous obtenons les mêmes résultats à chaque exécution, nous devons améliorer notre requête en ajoutant la colonne ID à ORDER BY clause, de cette façon nous nous assurons que ORDER BY La clause spécifie un ensemble unique de lignes, car la colonne id contient un index de clé primaire.

Exécutons maintenant la requête améliorée ci-dessous :

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)

Maintenant, nous sommes sûrs que nous pouvons obtenir un échantillon aléatoire reproductible en utilisant cette méthode.

Il est temps de voir EXPLAIN ANALYZE résultats de notre requête 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)

Pour comparer cette méthode avec TABLESAMPLE méthodes, choisissons SYSTEM méthode avec REPEATABLE option, car cette méthode nous donne des résultats reproductibles.

TABLESAMPLE SYSTEM la méthode effectue essentiellement un échantillonnage au niveau du bloc/de la page ; il lit et renvoie des pages aléatoires à partir du disque. Ainsi, il fournit un caractère aléatoire au niveau de la page au lieu du niveau du tuple. C'est pourquoi il est difficile de contrôler exactement le nombre de lignes récupérées. S'il n'y a pas de pages sélectionnées, nous n'obtiendrons peut-être aucun résultat. L'échantillonnage au niveau de la page est également sujet à l'effet de regroupement.

TABLESAMPLE La SYNTAXE est la même pour BERNOULLI et SYSTEM méthodes, nous spécifierons le pourcentage comme nous l'avons fait dans BERNOULLI méthode.

Rappel rapide : SYNTAXE

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

Afin de récupérer environ 100 lignes, nous devons demander 100 * 100 / 1 000 000 =0,01 % des lignes du tableau. Notre pourcentage sera donc de 0,01.

Avant de commencer à interroger, rappelons-nous comment REPEATABLE œuvres. Fondamentalement, nous choisirons un paramètre de graine et nous obtiendrons les mêmes résultats à chaque fois que nous utiliserons la même graine avec la requête précédente. Si nous fournissons une graine différente, la requête produira un ensemble de résultats assez différent.

Essayons de voir les résultats avec l'interrogation.

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)

Nous obtenons 136 lignes, car vous pouvez considérer que ce nombre dépend du nombre de lignes stockées sur une seule page.

Essayons maintenant d'exécuter la même requête avec le même numéro de départ :

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)

Nous obtenons le même jeu de résultats grâce à REPEATABLE option. Nous pouvons maintenant comparer TABLESAMPLE SYSTEM méthode avec la méthode de colonne aléatoire en regardant le EXPLAIN ANALYZE sortie.

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 La méthode utilise l'analyse d'échantillons et elle est extrêmement rapide. Le seul revers de cette méthode est que nous ne pouvons pas garantir que le pourcentage fourni se traduira par le nombre de lignes attendu.

Conclusion

Dans cet article de blog, nous avons comparé la norme TABLESAMPLE (SYSTEM , BERNOULLI ) et les tsm_system_rows module avec les anciennes méthodes aléatoires.

Ici, vous pouvez consulter rapidement les résultats :

  • ORDER BY random() est lent à cause du tri
  • random() <= 0.1 est similaire à BERNOULLI méthode
  • L'ajout d'une colonne avec une valeur aléatoire nécessite un travail initial et peut entraîner des problèmes de performances
  • SYSTEM est rapide mais il est difficile de contrôler le nombre de lignes
  • tsm_system_rows est rapide et peut contrôler le nombre de lignes

En conséquence tsm_system_rows bat toute autre méthode pour sélectionner seulement quelques lignes aléatoires.

Mais le vrai gagnant est définitivement TABLESAMPLE lui-même. Grâce à son extensibilité, les extensions personnalisées (c'est-à-dire tsm_system_rows , tsm_system_time ) peut être développé à l'aide de TABLESAMPLE fonctions de méthode.

Remarque du développeur : Vous trouverez plus d'informations sur la façon d'écrire des méthodes d'échantillonnage personnalisées dans le chapitre Écrire une méthode d'échantillonnage de table de la documentation PostgreSQL.

Remarque pour le futur : Nous discuterons du projet AXLE et de l'extension tsm_system_time dans notre prochain TABLESAMPLE article de blog.