PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Tablesample und andere Methoden zum Abrufen zufälliger Tupel

TABLESAMPLE von PostgreSQL bietet einige weitere Vorteile im Vergleich zu anderen traditionellen Methoden, um zufällige Tupel zu erhalten.

TABLESAMPLE ist eine SQL SELECT-Klausel und bietet zwei Stichprobenmethoden, nämlich SYSTEM und BERNOULLI . Mit Hilfe von TABLESAMPLE Wir können ganz einfach zufällige Zeilen aus einer Tabelle abrufen. Weitere Informationen zu TABLESAMPLE finden Sie im vorherigen Blogpost .

In diesem Blogbeitrag sprechen wir über alternative Möglichkeiten, zufällige Zeilen zu erhalten. Wie Leute vor TABLESAMPLE zufällige Zeilen ausgewählt haben , was sind die Vor- und Nachteile der anderen Methoden und was wir mit TABLESAMPLE gewonnen haben ?

Es gibt tolle Blogposts zum Auswählen zufälliger Zeilen. Sie können mit dem Lesen der folgenden Blogposts beginnen, um ein tieferes Verständnis dieses Themas zu erlangen.

Meine Gedanken zum Erhalten einer zufälligen Zeile

Abrufen zufälliger Zeilen aus einer Datenbanktabelle

random_agg()

Lassen Sie uns die traditionellen Methoden zum Abrufen zufälliger Zeilen aus einer Tabelle mit den neuen Methoden von TABLESAMPLE vergleichen.

Vor dem TABLESAMPLE -Klausel gab es 3 häufig verwendete Methoden zum zufälligen Auswählen von Zeilen aus einer Tabelle.

1- Zufällige Bestellung()

Zu Testzwecken müssen wir eine Tabelle erstellen und einige Daten darin einfügen.

Lassen Sie uns eine ts_test-Tabelle erstellen und 1 Million Zeilen darin einfügen:

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

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

Betrachten Sie die folgende SQL-Anweisung zum Auswählen von 10 zufälligen Zeilen:

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

Bewirkt, dass PostgreSQL einen vollständigen Tabellenscan und auch eine Sortierung durchführt. Daher wird diese Methode aus Performance-Gründen nicht für Tabellen mit vielen Zeilen bevorzugt.

Schauen wir uns EXPLAIN ANALYZE an Ausgabe dieser Abfrage oben:

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 weist darauf hin, dass die Auswahl von 10 aus 1 Mio. Zeilen fast 2 Sekunden dauerte. Die Abfrage verwendete auch die Ausgabe von random() als Sortierschlüssel zum Sortieren der Ergebnisse. Das Sortieren scheint hier die zeitaufwändigste Aufgabe zu sein. Lassen Sie uns dies mit einem Szenario mit TABLESAMPLE ausführen .

In PostgreSQL 9.5 können wir die Stichprobenmethode SYSTEM_ROWS verwenden, um die genaue Anzahl der Zeilen zufällig zu erhalten. Bereitgestellt von tsm_system_rows contrib-Modul können wir angeben, wie viele Zeilen durch Stichproben zurückgegeben werden sollen. Normalerweise konnte mit TABLESAMPLE SYSTEM nur der angeforderte Prozentsatz angegeben werden und BERNOULLI Methoden.

Zuerst sollten wir tsm_system_rows erstellen Erweiterung für die Verwendung dieser Methode, da sowohl TABLESAMPLE SYSTEM und TABLESAMPLE BERNOULLI Methoden garantieren nicht, dass der angegebene Prozentsatz zu der erwarteten Anzahl von Zeilen führt. Bitte überprüfen Sie das vorherige TABLESAMPLE p ost, um sich daran zu erinnern, warum sie so funktionieren.

Beginnen wir mit der Erstellung von tsm_system_rows Erweiterung:

random=# CREATE EXTENSION tsm_system_rows;
CREATE EXTENSION

Vergleichen wir nun „ORDER BY random()EXPLAIN ANALYZE Ausgabe mit EXPLAIN ANALYZE Ausgabe von tsm_system_rows Abfrage, die 10 zufällige Zeilen aus einer Tabelle mit 1 Million Zeilen zurückgibt.

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)

Die gesamte Abfrage dauerte 0,159 ms. Diese Stichprobenmethode ist im Vergleich zu „ORDER BY random()“ extrem schnell ”-Methode, die 1956,9 ms dauerte.

2- Vergleiche mit random()

Mit dem folgenden SQL können wir zufällige Zeilen mit einer Wahrscheinlichkeit von 10 % abrufen

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

Diese Methode ist schneller als das Sortieren nach dem Zufallsprinzip, da ausgewählte Zeilen nicht sortiert werden. Es gibt den ungefähren Prozentsatz der Zeilen aus der Tabelle zurück, genau wie BERNOULLI oder SYSTEM TABLESAMPLE Methoden.

Sehen wir uns die EXPLAIN ANALYZE an Ausgabe von random() Abfrage oben:

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)

Die Abfrage dauerte 367,5 ms. Viel besser als ORDER BY random() . Aber es ist schwieriger, die genaue Zeilenanzahl zu kontrollieren. Vergleichen wir „random()EXPLAIN ANALYZE Ausgabe mit EXPLAIN ANALYZE Ausgabe von TABLESAMPLE BERNOULLI Abfrage, die etwa 10 % der zufälligen Zeilen aus der Tabelle mit 1 Million Zeilen zurückgibt.

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)

Wir haben BERNOULLI 10 als Parameter gegeben weil wir 10 % aller Datensätze benötigen.

Hier sehen wir, dass der BERNOULLI Die Ausführung der Methode dauerte 239,289 ms. Diese beiden Methoden sind in ihrer Funktionsweise ziemlich ähnlich, BERNOULLI ist etwas schneller, da die zufällige Auswahl auf einer niedrigeren Ebene erfolgt. Ein Vorteil der Verwendung von BERNOULLI verglichen mit WHERE random() <= 0.1 ist der REPEATABLE -Klausel, die wir im vorherigen TABLESAMPLE beschrieben haben posten.

3- Zusätzliche Spalte mit einem zufälligen Wert

Diese Methode schlägt vor, eine neue Spalte mit zufällig zugewiesenen Werten hinzuzufügen, ihr einen Index hinzuzufügen, eine Sortierung nach dieser Spalte durchzuführen und optional ihre Werte regelmäßig zu aktualisieren, um die Verteilung zufällig zu gestalten.

Diese Strategie ermöglicht eine weitgehend wiederholbare Zufallsstichprobe. Es funktioniert viel schneller als die erste Methode, aber die erstmalige Einrichtung ist mühsam und führt zu Leistungseinbußen bei Einfüge-, Aktualisierungs- und Löschvorgängen.

Wenden wir diese Methode auf unseren ts_test an Tabelle.

Zuerst fügen wir eine neue Spalte namens randomcolumn hinzu mit dem Typ mit doppelter Genauigkeit, weil PostgreSQL random() Funktion gibt eine Zahl mit doppelter Genauigkeit zurück.

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

Dann aktualisieren wir die neue Spalte mit 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

Diese Methode hat anfängliche Kosten für das Erstellen einer neuen Spalte und das Füllen dieser neuen Spalte mit zufälligen (0,0-1,0) Werten, aber es sind einmalige Kosten. In diesem Beispiel dauerte es für 1 Million Zeilen fast 8,5 Sekunden.

Versuchen wir zu beobachten, ob unsere Ergebnisse reproduzierbar sind, indem wir 100 Zeilen mit unserer neuen Methode abfragen:

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)

Wenn wir die obige Abfrage ausführen, erhalten wir meistens dieselbe Ergebnismenge, aber dies ist nicht garantiert, da wir random() verwendet haben Funktion zum Füllen von randomcolumn Werte und in diesem Fall können mehr als eine Spalte den gleichen Wert haben. Um sicherzustellen, dass wir bei jeder Ausführung die gleichen Ergebnisse erhalten, sollten wir unsere Abfrage verbessern, indem wir die ID-Spalte zu ORDER BY hinzufügen Klausel, auf diese Weise stellen wir sicher, dass ORDER BY -Klausel gibt einen eindeutigen Satz von Zeilen an, da die ID-Spalte einen Primärschlüsselindex enthält.

Lassen Sie uns nun die verbesserte Abfrage unten ausführen:

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)

Jetzt sind wir sicher, dass wir mit dieser Methode eine reproduzierbare Zufallsstichprobe erhalten können.

Es ist an der Zeit, EXPLAIN ANALYZE anzusehen Ergebnisse unserer letzten Abfrage:

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)

Zum Vergleich dieser Methode mit TABLESAMPLE Methoden, wählen wir SYSTEM Methode mit REPEATABLE Option, da wir mit dieser Methode reproduzierbare Ergebnisse erhalten.

TABLESAMPLE SYSTEM Methode führt im Grunde Stichproben auf Block-/Seitenebene durch; es liest zufällige Seiten von der Festplatte und gibt sie zurück. Somit stellt es Zufälligkeit auf Seitenebene statt auf Tupelebene bereit. Aus diesem Grund ist es schwierig, die Anzahl der abgerufenen Zeilen genau zu steuern. Wenn keine Seiten ausgewählt wurden, erhalten wir möglicherweise kein Ergebnis. Das Sampling auf Seitenebene ist auch anfällig für Clustering-Effekte.

TABLESAMPLE SYNTAX ist für BERNOULLI gleich und SYSTEM Methoden geben wir den Prozentsatz an, wie wir es in BERNOULLI getan haben Methode.

Kurze Erinnerung: SYNTAX

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

Um ungefähr 100 Zeilen abzurufen, müssen wir 100 * 100 / 1 000 000 =0,01 % der Zeilen der Tabelle anfordern. Unser Prozentsatz beträgt also 0,01.

Bevor wir mit der Abfrage beginnen, erinnern wir uns daran, wie REPEATABLE funktioniert. Grundsätzlich wählen wir einen Seed-Parameter und erhalten jedes Mal dieselben Ergebnisse, wenn wir denselben Seed mit der vorherigen Abfrage verwenden. Wenn wir einen anderen Startwert bereitstellen, wird die Abfrage eine ganz andere Ergebnismenge erzeugen.

Lassen Sie uns versuchen, die Ergebnisse mit Abfragen anzuzeigen.

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)

Wir erhalten 136 Zeilen, da Sie davon ausgehen können, dass diese Zahl davon abhängt, wie viele Zeilen auf einer einzelnen Seite gespeichert sind.

Versuchen wir nun, dieselbe Abfrage mit derselben Seed-Nummer auszuführen:

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)

Dank REPEATABLE erhalten wir dieselbe Ergebnismenge Möglichkeit. Jetzt können wir TABLESAMPLE SYSTEM vergleichen Methode mit zufälliger Spaltenmethode, indem Sie EXPLAIN ANALYZE betrachten Ausgabe.

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 verwendet Sample-Scan und ist extrem schnell. Der einzige Nachteil dieser Methode ist, dass wir nicht garantieren können, dass der angegebene Prozentsatz zu der erwarteten Anzahl von Zeilen führt.

Schlussfolgerung

In diesem Blogbeitrag haben wir Standard-TABLESAMPLE verglichen (SYSTEM , BERNOULLI ) und die tsm_system_rows Modul mit den älteren Zufallsmethoden.

Hier können Sie die Ergebnisse schnell überprüfen:

  • ORDER BY random() ist wegen der Sortierung langsam
  • random() <= 0.1 ist ähnlich wie BERNOULLI Methode
  • Das Hinzufügen einer Spalte mit zufälligen Werten erfordert anfängliche Arbeit und kann zu Leistungsproblemen führen
  • SYSTEM ist schnell, aber es ist schwer, die Anzahl der Zeilen zu kontrollieren
  • tsm_system_rows ist schnell und kann die Anzahl der Zeilen steuern

Als Ergebnis tsm_system_rows schlägt jede andere Methode, um nur wenige zufällige Zeilen auszuwählen.

Aber der wahre Gewinner ist definitiv TABLESAMPLE selbst. Dank seiner Erweiterbarkeit können benutzerdefinierte Erweiterungen (z. B. tsm_system_rows , tsm_system_time ) kann mit TABLESAMPLE entwickelt werden Methodenfunktionen.

Entwicklerhinweis: Weitere Informationen zum Schreiben benutzerdefinierter Sampling-Methoden finden Sie im Kapitel Writing A Table Sampling Method der PostgreSQL-Dokumentation.

Hinweis für die Zukunft: Wir werden das AXLE-Projekt und die Erweiterung tsm_system_time in unserem nächsten TABLESAMPLE besprechen Blogbeitrag.