Was aber, wenn der Optimierer die tatsĂ€chlichen Werte fĂŒr Kosten, SelektivitĂ€t und andere erforderliche Parameter fĂŒr die AusfĂŒhrung von Abfragen beibehĂ€lt und sich bei Wiederholung nicht nur auf die standardmĂ€Ăig gesammelten Statistiken konzentriert, sondern auch auf diejenigen, die nach der vorherigen AusfĂŒhrung gespeichert wurden?
Dies wird als adaptive Abfrageoptimierung bezeichnet und ist vielversprechend. Einige DBMS verwenden solche Technologien bereits.
Unternehmen Postgres Professional arbeitet seit mehreren Jahren an der AQO-Erweiterung fĂŒr PostgreSQL, die (in irgendeiner Form) adaptive Optimierung implementiert. Die Arbeiten sind noch im Gange, aber es gibt bereits etwas zu testen.
Schauen wir uns zunÀchst den Themenbereich der Abfrageoptimierung genauer an.
Warum ein Planer einen suboptimalen Plan auswÀhlen kann
SQL-Abfragen können auf verschiedene Arten ausgefĂŒhrt werden. Wenn beispielsweise zwei Tabellen verknĂŒpft sind, kann dies auf verschiedene Arten erfolgen - mithilfe verschachtelter Schleifen, ZusammenfĂŒhren und Hashing. Je mehr Tabellen an der Abfrage beteiligt sind, desto mehr Variationen ihrer VerknĂŒpfungen. Die Aufgabe des Planers besteht darin, aus vielen Variationen den AusfĂŒhrungsplan der Abfrage mit den niedrigsten Kosten auszuwĂ€hlen.
Wie bereits erwÀhnt, verwenden die Scheduler vieler DBMS in ihrer Arbeit statistische Informationen, die entweder automatisch oder manuell erfasst werden. Der Planer berechnet anhand dieser Statistiken die geschÀtzten Kosten.
Im Allgemeinen funktionieren die Planer moderner DBMS in den meisten Situationen gut. In einigen FÀllen kann der gewÀhlte Plan jedoch weit vom Optimum entfernt sein.
Beispielsweise,Das Fehlen aktueller statistischer Informationen fĂŒhrt dazu, dass sich der Planer bei seiner Arbeit an (höchstwahrscheinlich) falschen Daten zur Anzahl der Zeilen in den zu verbindenden Tabellen orientiert. ĂbermĂ€Ăiges Understatement (oder Ăbertreiben) der KardinalitĂ€t fĂŒhrt zur Auswahl nicht optimaler Methoden fĂŒr den Zugriff auf Daten in Tabellen.
Ein weiterer wichtiger Grund kann das Fehlen notwendiger Indizes sein . In Ermangelung von Indizes hat der Scheduler eine begrenzte Auswahl an Datenzugriffsmethoden.
Verwendung abhĂ€ngiger (korrelierter) Bedingungenkann sich auch negativ auf den Betrieb des DBMS auswirken. Der Scheduler geht (standardmĂ€Ăig) davon aus, dass alle Bedingungen in einer Abfrage unabhĂ€ngig voneinander sind, dh der Wert einer Bedingung wirkt sich in keiner Weise auf die andere aus. Dies ist nicht immer der Fall. Wenn abhĂ€ngige Bedingungen verwendet werden (z. B. Postleitzahl und Stadt), berechnet der Planer auch die falschen Kosten und die falsche KardinalitĂ€t der Verbindungen.
Die Verwendung von Funktionen in Bedingungen kann sich auch auf den Scheduler auswirken . Eine Funktion fĂŒr den Planer ist eine âBlack Boxâ. Er weiĂ nicht, wie viele Zeilen die Funktion zurĂŒckgeben wird, was auch zu fehlerhaften Plankosten fĂŒhren kann.
Möglichkeiten, die Arbeit des Schedulers zu beeinflussen
Aktuelle Statistiken sind eine unabdingbare Voraussetzung fĂŒr die angemessene Arbeit des Schedulers. Stellen Sie zunĂ€chst sicher, dass das System so konfiguriert ist, dass regelmĂ€Ăig statistische Informationen erfasst werden.
Es gibt verschiedene Möglichkeiten, um die oben beschriebenen Situationen zu beheben und dem Planer bei der Auswahl der optimalsten AbfrageausfĂŒhrungsplĂ€ne zu helfen.
Ohne Indizes hat der Scheduler nur eine Möglichkeit, Daten zu erhalten - das sequentielle Scannen von Tabellen (und dies ist nicht immer schlecht und teuer). In einigen FĂ€llen kann das Erstellen der erforderlichen Indizes den Datenzugriff beschleunigen. Sie mĂŒssen nicht die gesamte Tabelle scannen. Die Verwendung von Indizes (Suche nach dem Notwendigen, Erstellung, Pflege) ist jedoch nicht kostenlos. Idealerweise sollten sie genau dort eingesetzt werden, wo sie benötigt werden. Und wo nicht benötigt - nicht verwenden.
Wenn korreliert mit Join - Bedingungen in Abfragen können Sie erzeugen eine erweiterte Statistik- den Optimierer explizit auffordern, dass die Bedingungen miteinander zusammenhĂ€ngen. Dazu muss der DBA (oder Entwickler) seine Daten gut kennen und abhĂ€ngige Bedingungen in Abfragen verfolgen, da die Anzahl der Kombinationen abhĂ€ngiger Spalten im Voraus schwer vorherzusagen ist. Erweiterte Statistiken mĂŒssen fĂŒr jede dieser Optionen manuell erstellt werden.
Beim Erstellen einer Funktion können Sie die ungefĂ€hren AusfĂŒhrungskosten und / oder eine SchĂ€tzung der Anzahl der von der Funktion zurĂŒckgegebenen Zeilen angeben . In Version 12 wurde es möglich, Hilfsfunktionen zu verwenden, um die SchĂ€tzungen des Planers in AbhĂ€ngigkeit von den Argumenten zu verbessern. Dies ist auch ein manueller Weg, der nicht immer ein optimales Ergebnis liefert.
Wenn alles andere fehlschlÀgt, können Sie die Abfrage manuell neu schreibenVerwenden Sie beispielsweise materialisierte Ansichten, Common Table Expressions (CTE). Oder um die Anforderungen des Themenbereichs zu klÀren und möglicherweise die Logik der Anforderung radikal umzuschreiben.
Und es ist eine andere Art von âHinweiseâ an den Scheduler - adaptive Abfrageoptimierung ( a daptive q uery o ptimization). Die Idee dieser Methode ist, dass nach der AusfĂŒhrung der Abfrage echte statistische Informationen gespeichert werden und der Optimierer sich darauf verlassen kann, wenn die angegebene (oder Ă€hnliche) Abfrage wiederholt wird.
Das DBMS Postgres Pro Enterprise ist eine Erweiterung fĂŒr die adaptive Abfrageoptimierung mit dem Namen AQO... Diese Erweiterung ist auf github veröffentlicht: github.com/postgrespro/aqo , Sie können sie mit Vanilla PostgreSQL ausprobieren, mehr dazu weiter unten.
Das Funktionsprinzip des Moduls
Das AQO-Modul verwendet in seiner Arbeit maschinelles Lernen. Weitere Informationen zum Funktionsprinzip finden Sie in dem Artikel von Oleg Ivanov ĂŒber maschinelles Lernen zur Steigerung der PostgreSQL-Leistung und ausfĂŒhrlicher in der PrĂ€sentation Adaptive Query Optimization (Bericht auf YouTube ).
Das Wesentliche dieser Methode wird im Folgenden kurz beschrieben:
Um die Kosten zu bewerten, benötigt der Planer eine Bewertung der KardinalitĂ€t, und dafĂŒr ist wiederum eine Bewertung der SelektivitĂ€t der Bedingungen erforderlich.
FĂŒr einfache Bedingungen (wie âAttribut = Konstanteâ oder âAttribut> Konstanteâ) verfĂŒgt der Scheduler ĂŒber ein Modell, anhand dessen er die SelektivitĂ€t schĂ€tzt. Dazu verwendet er statistische Informationen: die Anzahl der eindeutigen Attributwerte, Histogramme usw.
FĂŒr Bedingungen, die aus einfachen Elementen bestehen, die logische VerknĂŒpfungen verwenden, verwendet der Planer einfach zu berechnende Formeln:
- sel (nicht A) = 1 - sel (A)
- sel (nicht A) = 1 - sel (A)
- sel (A und B) = sel (A) * sel (B)
- sel (A oder B) = sel (nicht (nicht A und nicht B)) = 1 - (1 - sel (A)) * (1 - sel (B))
Diese Formeln setzen die UnabhÀngigkeit (Unkorrelation) der Bedingungen A und B voraus, weshalb wir falsche SchÀtzungen erhalten, wenn diese Annahme verletzt wird.
AQO verkompliziert die Formel: fĂŒhrt fĂŒr jede einfache Bedingung einen eigenen Koeffizienten ein. Unter Verwendung von maschinellem Lernen (unter Verwendung der Regression des nĂ€chsten Nachbarn) passt AQO diese Koeffizienten so an, dass die durch die Formel berechnete SelektivitĂ€t am besten mit der tatsĂ€chlichen SelektivitĂ€t ĂŒbereinstimmt, die AQO zuvor beobachtet hat.
Dazu speichert das Modul:
- , ;
- .
AQO unterscheidet in seiner Arbeit zwischen Bedingungen bis hin zu Konstanten. Dies ermöglicht es, die KomplexitĂ€t des zu lösenden Problems zu verringern, und auĂerdem gehen in den meisten FĂ€llen immer noch keine Informationen verloren: AQO "sieht" den Wert der Konstante nicht, sondern "sieht" die SelektivitĂ€t der Bedingung.
Die Situation, in der der Verlust immer noch auftritt, sind die Bedingungen, die unabhĂ€ngig von den spezifischen Werten als Konstante bewertet werden. Beispielsweise kann der Planer fĂŒr einige Bedingungen keine vernĂŒnftigen SchĂ€tzungen vornehmen und wĂ€hlt eine Standardkonstante (zum Beispiel wird die SelektivitĂ€t der Bedingung "Ausdruck1 = Ausdruck2" immer mit 0,005 und "Ausdruck1> Ausdruck2" mit 1/3 bewertet).
Somit kann AQO die Bewertung der SelektivitĂ€t komplexer Bedingungen verbessern (und infolgedessen die Bewertung der Kosten, was zur Wahl eines angemesseneren Umsetzungsplans fĂŒhren kann).
Modulinstallation
Um die FunktionalitĂ€t des Moduls unter Vanilla PostgreSQL zu testen, mĂŒssen Sie einen speziellen Patch verwenden und das System dann aus dem Quellcode erstellen. Weitere Informationen finden Sie in der README-Datei auf github.
Wenn Postgres Pro Enterprise verwendet wird, wird die Installation des AQO-Moduls im Standardmodus fortgesetzt:
shared_preload_libraries = 'aqo'
Danach können Sie eine Erweiterung in der erforderlichen Datenbank erstellen.
Datenbankvorbereitung
Schauen wir uns ein konkretes Beispiel an, wie das AQO-Modul in einer Demo-Datenbank funktioniert . Wir werden eine groĂe Datenbank mit Informationen zu FlĂŒgen fĂŒr das Jahr von September 2016 bis September 2017 verwenden.
Erstellen wir zunÀchst eine Erweiterung:
CREATE EXTENSION aqo;
Deaktivieren Sie als NÀchstes die parallele Abfrageverarbeitung, damit die Anzeige paralleler PlÀne nicht von der Hauptaufgabe ablenkt:
max_parallel_workers_per_gather = 0;
Damit der PostgreSQL-Scheduler mehr Optionen zum VerknĂŒpfen von Tabellen bietet, erstellen Sie zwei Indizes:
CREATE INDEX ON flights (scheduled_departure );
CREATE INDEX ON ticket_flights (fare_conditions );
Bei der Analyse der Ergebnisse konzentrieren wir uns auf den Wert von BUFFERS als die Anzahl der Seiten, die gelesen werden mĂŒssen, um die Aufgabe auszufĂŒhren. Schauen wir uns auch die AusfĂŒhrungszeit an (aber die Zeit auf einem geladenen System und einem Heim-Laptop kann sehr unterschiedlich sein).
Erhöhen wir den Puffercache und work_mem, damit alle Arbeiten im RAM ausgefĂŒhrt werden:
shared_buffers = '256MB';
work_mem = '256MB';
Verwenden des AQO-Moduls
Wir werden eine Anfrage formulieren: Es ist erforderlich, Passagiere zu empfangen, die ab einem bestimmten Datum eine Business Class geflogen sind und spÀtestens mit einer Stunde verspÀtet angekommen sind.
Lassen Sie uns die Anforderung ohne Verwendung von AQO ausfĂŒhren (im Folgenden wurde ein Teil der Informationen, die das VerstĂ€ndnis der Funktionsweise des Moduls nicht beeintrĂ€chtigen, aus den PlĂ€nen entfernt):
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF) SELECT t.ticket_no
FROM flights f
JOIN ticket_flights tf ON f.flight_id = tf.flight_id
JOIN tickets t ON tf.ticket_no = t.ticket_no
WHERE f.scheduled_departure > '2017-08-01'::timestamptz
AND f.actual_arrival < f.scheduled_arrival + interval '1 hour'
AND tf.fare_conditions = 'Business';
Und sehen wir uns den resultierenden Plan an: In diesem Fall hat der Planer den optimalen Plan betrachtet, bei dem wir zuerst mithilfe eines Bitmap-Scans ( ) eine Reihe von Zeilen aus der Flugtabelle erhalten , die wir (einen Knoten ) mit einer Reihe von Zeilen aus der Tabelle ticket_flights haben, die unter Verwendung des Index erhalten wurden scan ( ). Das Ergebnis wird als externer Satz von Zeilen fĂŒr die endgĂŒltige Verbindung durch eine verschachtelte Schleife (Knoten ) verwendet. Der innere Satz fĂŒr diesen Join wird durch einen exklusiven Index-Scan der Tickets ( ) -Tabelle erhalten . Die "sperrigste" Operation besteht darin, das interne Rowset fĂŒr die verschachtelte Schleife abzurufen - 106 205 Puffer werden darin eingelesen.
Nested Loop (rows=33210) (actual rows=31677)
Buffers: shared hit=116830 read=1
-> Hash Join (rows=33210) (actual rows=31677)
Hash Cond: (tf.flight_id = f.flight_id)
-> Index Scan ... on ticket_flights tf
Index Cond: fare_conditions = 'Business'
-> Hash
-> Bitmap Heap Scan on flights f (rows=8331) (actual rows=7673)
Recheck Cond: scheduled_departure > '2017-08-01'
Filter: actual_arrival < scheduled_arrival + '01:00:00'::interval
-> Bitmap Index Scan on ... [flights]
Index Cond: scheduled_departure > '2017-08-01'
Buffers: shared hit=44 read=1
-> Index Only Scan ... on tickets t (rows=1 width=14) (actual rows=1 loops=31677)
Index Cond: (ticket_no = tf.ticket_no)
Buffers: shared hit=106205
Planning Time: 9.326 ms
Execution Time: 675.836 ms
Bitmap Heap Scan on flights
Hash Join
Index Scan ... on ticket_flights
Nested Loop
Index Only Scan ... on tickets
Dieser Plan kann als relativ gut bezeichnet werden, da eine verschachtelte Schleife eine relativ kleine Anzahl von Zeilen in der Ă€uĂeren Menge verbindet.
Jetzt fĂŒhren wir ein Experiment durch und sehen, wie sich der vorgeschlagene Plan abhĂ€ngig von der Ănderung der Daten in der Anfrage Ă€ndern wird (oder nicht). Daten werden so ausgewĂ€hlt, dass der Zeilenbereich der Flugtabelle, der die Bedingung erfĂŒllt, konsistent vergröĂert wird, was zu einem Planungsfehler bei der Bewertung der KardinalitĂ€t des Zugriffs auf diese Tabelle fĂŒhrt. Im obigen Plan können Sie sehen, dass der Optimierer beim ersten Datum fast nicht in der KardinalitĂ€t verwechselt wird ( ). Ersetzen Sie die folgenden Daten in der Anfrage:
Bitmap Heap Scan on flights f (rows=8331) (actual rows=7673)
- 2017-04-01
- 2017-01-01
- 2016-08-01
Und sehen wir uns das Ergebnis an:
AbfrageplÀne ohne AQO
2017-04-01
Nested Loop (rows=31677) (actual rows=292392)
Buffers: shared hit=991756
-> Hash Join (rows=31677) (actual rows=292392)
Hash Cond: (tf.flight_id = f.flight_id)
-> Index Scan ⊠on ticket_flights tf
Index Cond: fare_conditions = 'Business')
-> Hash
-> Bitmap Heap Scan on flights f (rows=7673) (actual rows=70553)
Recheck Cond: scheduled_departure > '2017-04-01'
Filter: actual_arrival < (scheduled_arrival + '01:00:00'::interval)
-> Bitmap Index Scan on ... [flights]
Index Cond: scheduled_departure > '2017-04-01'
Buffers: shared hit=160
-> Index Only Scan ... on tickets t ( rows=1 width=14) (actual rows=1 loops=292392)
Index Cond: (ticket_no = tf.ticket_no)
Buffers: shared hit=980995
Planning Time: 5.980 ms
Execution Time: 2771.563 ms
, . . , (
() â Flights , ( , ).
2017-01-01
Nested Loop (rows=187710) (actual rows=484569)
Buffers: shared hit=1640723 read=49
-> Hash Join (rows=187738) (actual rows=484569)
Hash Cond: (tf.flight_id = f.flight_id)
-> Index Scan ... on ticket_flights tf
Index Cond: fare_conditions = 'Business'
-> Hash
-> Seq Scan on flights f (rows=45352) (actual rows=116985)
Filter: scheduled_departure > '2017-01-01'::date
AND actual_arrival < scheduled_arrival + '01:00:00'::interval
-> Index Only Scan ... on tickets t (rows=1) (actual rows=1 loops=484569)
Index Cond: (ticket_no = tf.ticket_no)
Buffers: shared hit=1630118 read=49
Planning Time: 6.225 ms
Execution Time: 4498.741 ms
, . flights, ( ) .
tickets â (1 630 118).
2016-08-01
Hash Join (rows=302200) (actual rows=771441)
Hash Cond: (t.ticket_no = tf.ticket_no)
Buffers: shared hit=25499 read=34643
-> Seq Scan on tickets t (rows=2949857) (actual rows=2949857)
-> Hash
-> Hash Join (rows=302236) (actual rows=771441)
Hash Cond: (tf.flight_id = f.flight_id)
-> Index Scan on ticket_flights tf
Index Cond: fare_conditions = 'Business'
-> Hash
-> Seq Scan on flights f (rows=73005) (actual rows=188563)
Filter: scheduled_departure > '2016-08-01'::date)
AND actual_arrival < scheduled_arrival + '01:00:00'::interval
Planning Time: 9.990 ms
Execution Time: 3014.577 ms
(
Nested Loop (rows=31677) (actual rows=292392)
Buffers: shared hit=991756
-> Hash Join (rows=31677) (actual rows=292392)
Hash Cond: (tf.flight_id = f.flight_id)
-> Index Scan ⊠on ticket_flights tf
Index Cond: fare_conditions = 'Business')
-> Hash
-> Bitmap Heap Scan on flights f (rows=7673) (actual rows=70553)
Recheck Cond: scheduled_departure > '2017-04-01'
Filter: actual_arrival < (scheduled_arrival + '01:00:00'::interval)
-> Bitmap Index Scan on ... [flights]
Index Cond: scheduled_departure > '2017-04-01'
Buffers: shared hit=160
-> Index Only Scan ... on tickets t ( rows=1 width=14) (actual rows=1 loops=292392)
Index Cond: (ticket_no = tf.ticket_no)
Buffers: shared hit=980995
Planning Time: 5.980 ms
Execution Time: 2771.563 ms
, . . , (
Bitmap Heap Scan on flights f (rows=7673) (actual rows=70553)
), , Nested Loop, , .
() â Flights , ( , ).
2017-01-01
Nested Loop (rows=187710) (actual rows=484569)
Buffers: shared hit=1640723 read=49
-> Hash Join (rows=187738) (actual rows=484569)
Hash Cond: (tf.flight_id = f.flight_id)
-> Index Scan ... on ticket_flights tf
Index Cond: fare_conditions = 'Business'
-> Hash
-> Seq Scan on flights f (rows=45352) (actual rows=116985)
Filter: scheduled_departure > '2017-01-01'::date
AND actual_arrival < scheduled_arrival + '01:00:00'::interval
-> Index Only Scan ... on tickets t (rows=1) (actual rows=1 loops=484569)
Index Cond: (ticket_no = tf.ticket_no)
Buffers: shared hit=1630118 read=49
Planning Time: 6.225 ms
Execution Time: 4498.741 ms
, . flights, ( ) .
tickets â (1 630 118).
2016-08-01
Hash Join (rows=302200) (actual rows=771441)
Hash Cond: (t.ticket_no = tf.ticket_no)
Buffers: shared hit=25499 read=34643
-> Seq Scan on tickets t (rows=2949857) (actual rows=2949857)
-> Hash
-> Hash Join (rows=302236) (actual rows=771441)
Hash Cond: (tf.flight_id = f.flight_id)
-> Index Scan on ticket_flights tf
Index Cond: fare_conditions = 'Business'
-> Hash
-> Seq Scan on flights f (rows=73005) (actual rows=188563)
Filter: scheduled_departure > '2016-08-01'::date)
AND actual_arrival < scheduled_arrival + '01:00:00'::interval
Planning Time: 9.990 ms
Execution Time: 3014.577 ms
(
(rows=302236) (actual rows=771441)
). , , : Hash Join Nested Loop.
Zusammenfassend funktioniert der Planer ohne Verwendung des AQO-Moduls wie folgt:
Datum | Puffer | Zeit, ms | Ein Kommentar |
2017-08-01 | 116 831 | 675,836 | Es wird eine verschachtelte Schleife und ein Hash-Join verwendet. FlĂŒge und Tickets werden nach Index gescannt |
2017-04-01 | 991 756 | 2771,563 | Der gleiche Plan, aber nicht optimal. Bei der Auswahl des Zugriffs nach Index fĂŒr die Tabellen "FlĂŒge" und "Tickets" wird festgestellt, dass der Planer bei der Berechnung der KardinalitĂ€t sehr falsch ist |
2017-01-01 | 1,640,772 | 4498.741 | Der gleiche suboptimale Plan. Der Planer beschlieĂt jedoch, zu einem sequentiellen Scan der Flugtabelle zu wechseln. |
2016-08-01 | 60142 | 3014,577 | Der Plan hat sich endlich geĂ€ndert - der Optimierer erkennt, dass er viele Zeilen aus den Tabellen auswĂ€hlen muss, und wechselt daher zum sequentiellen Scannen der FlĂŒge und Tickets. Eine ineffektive (in diesem Fall) verschachtelte Schleife wird durch einen Hash-Join ersetzt. |
AbfrageplÀne mit AQO
AQO. :
, :
2017-08-01
, , . AQO .
2017-04-01
Hash Join (rows=293891) (actual rows=292392)
Hash Cond: (t.ticket_no = tf.ticket_no)
Buffers: shared hit=25658 read=34640
-> Seq Scan on tickets t (rows=2949857) (actual rows=2949857)
-> Hash
-> Hash Join (rows=293734) (actual rows=292392)
Hash Cond: (tf.flight_id = f.flight_id)
-> Index Scan ... on ticket_flights tf
Index Cond: (fare_conditions)::text = 'Business'::text
-> Hash
-> Bitmap Heap Scan on flights f
Recheck Cond: scheduled_departure > '2017-04-01'::date
Filter: actual_arrival < scheduled_arrival + '01:00:00'::interval
-> Bitmap Index Scan on ... [flights]
Index Cond: scheduled_departure > '2017-04-01'::date
Buffers: shared hit=160
Planning Time: 9.652 ms
Execution Time: 2218.074 ms
ââ, AQO â . Tickets . . , AQO.
2017-01-01
Hash Join (rows=484452) (actual rows=484569)
Hash Cond: (t.ticket_no = tf.ticket_no)
Buffers: shared hit=25534 read=34608
-> Seq Scan on tickets t (rows=2949857) (actual rows=2949857)
-> Hash (rows=484464) (actual rows=484569)
-> Hash Join (rows=484464) (actual rows=484569)
Hash Cond: (tf.flight_id = f.flight_id)
-> Index Scan ... on ticket_flights tf
Index Cond: fare_conditions::text = 'Business'::text
-> Hash
-> Seq Scan on flights f (rows=116971) (actual rows=116985)
Filter: scheduled_departure > '2017-01-01'::date
AND actual_arrival < scheduled_arrival + '01:00:00'::interval
Planning Time: 6.264 ms
Execution Time: 2746.485 ms
â Flights .
2016-08-01
.
SET aqo.mode = 'learn';
, :
2017-08-01
, , . AQO .
2017-04-01
Hash Join (rows=293891) (actual rows=292392)
Hash Cond: (t.ticket_no = tf.ticket_no)
Buffers: shared hit=25658 read=34640
-> Seq Scan on tickets t (rows=2949857) (actual rows=2949857)
-> Hash
-> Hash Join (rows=293734) (actual rows=292392)
Hash Cond: (tf.flight_id = f.flight_id)
-> Index Scan ... on ticket_flights tf
Index Cond: (fare_conditions)::text = 'Business'::text
-> Hash
-> Bitmap Heap Scan on flights f
Recheck Cond: scheduled_departure > '2017-04-01'::date
Filter: actual_arrival < scheduled_arrival + '01:00:00'::interval
-> Bitmap Index Scan on ... [flights]
Index Cond: scheduled_departure > '2017-04-01'::date
Buffers: shared hit=160
Planning Time: 9.652 ms
Execution Time: 2218.074 ms
ââ, AQO â . Tickets . . , AQO.
2017-01-01
Hash Join (rows=484452) (actual rows=484569)
Hash Cond: (t.ticket_no = tf.ticket_no)
Buffers: shared hit=25534 read=34608
-> Seq Scan on tickets t (rows=2949857) (actual rows=2949857)
-> Hash (rows=484464) (actual rows=484569)
-> Hash Join (rows=484464) (actual rows=484569)
Hash Cond: (tf.flight_id = f.flight_id)
-> Index Scan ... on ticket_flights tf
Index Cond: fare_conditions::text = 'Business'::text
-> Hash
-> Seq Scan on flights f (rows=116971) (actual rows=116985)
Filter: scheduled_departure > '2017-01-01'::date
AND actual_arrival < scheduled_arrival + '01:00:00'::interval
Planning Time: 6.264 ms
Execution Time: 2746.485 ms
â Flights .
2016-08-01
.
Schauen wir uns das Ergebnis noch einmal an:
Datum | Puffer | Zeit, ms | Ein Kommentar |
2017-08-01 | 116 831 | 662,966 | Der Plan ist der gleiche wie ohne Verwendung des Moduls |
2017-04-01 | 60298 | 2218.074 | Anhand der Modulhinweise versteht der Optimierer, dass eine groĂe Anzahl von Zeichenfolgen zum Verbinden geplant ist, und verbessert bereits in diesem Schritt den Plan, indem die verschachtelte Schleife durch einen Hash-Join ersetzt wird |
2017-01-01 | 60142 | 2746.485 | Der Plan hat sich etwas verbessert - anstatt auf die Bitmap zur Flugtabelle zuzugreifen, wird deren sequentieller Scan verwendet |
2016-08-01 | 60142 | 3253,861 | Der Plan blieb unverÀndert - der beste Plan in diesem Fall |
Zusammenfassen
Die Verwendung des AQO-Moduls zur adaptiven Abfrageoptimierung hat sowohl Vor- als auch Nachteile.
Einer der Vorteile der Verwendung eines Moduls besteht darin, dass Sie abhĂ€ngige Bedingungen in Abfragen nicht verfolgen mĂŒssen. In einigen FĂ€llen kann sich die Geschwindigkeit der AbfrageausfĂŒhrung erhöhen. Es gibt verschiedene Modi fĂŒr die Verwendung des Moduls. Mit AQO können Sie beispielsweise nur bestimmte Arten von Abfragen optimieren.
Unter den Nachteilen des Moduls können wir zusĂ€tzliche Gemeinkosten fĂŒr die Schulung und das Speichern von Statistiken in den Modulstrukturen herausgreifen. Die vom Modul gesammelten statistischen Informationen werden nicht an Replikate ĂŒbertragen.
Das AQO-Modul ist keine âSilberkugelâ aller möglichen Probleme des Schedulers. In einigen Situationen kann das Modul beispielsweise erweiterte Statistiken ersetzen (wenn es nicht von Hand erstellt wurde) oder irrelevante Statistiken nicht berĂŒcksichtigen. Das Modul erstellt jedoch nicht die erforderlichen Indizes und schreibt darĂŒber hinaus den Abfragetext nicht neu.
Daher sollten Sie das Modul nicht fĂŒr alle Anforderungen aktivieren. Ideale Optimierungskandidaten fĂŒr AQO sind Abfragen, bei denen der Fehler des Planers bei der Berechnung der KardinalitĂ€t der Knoten zu einem schlechten Plan fĂŒhrt. Aus irgendeinem Grund ist es nicht möglich, die Verfeinerung dieser SchĂ€tzung zu beeinflussen.