Mais que se passe-t-il si l'optimiseur enregistre les valeurs rĂ©elles de coĂ»t, de sĂ©lectivitĂ© et d'autres paramĂštres nĂ©cessaires pour exĂ©cuter une requĂȘte et, lors de sa nouvelle exĂ©cution, est guidĂ© non seulement par les statistiques collectĂ©es standard, mais Ă©galement par celles enregistrĂ©es aprĂšs l'exĂ©cution prĂ©cĂ©dente?
Cela s'appelle l'optimisation adaptative des requĂȘtes et est prometteur. Certains SGBD utilisent dĂ©jĂ de telles technologies.
Société Postgres Professional travaillant depuis plusieurs années sur l'extension AQO pour PostgreSQL, qui implémente (sous une forme ou une autre) l'optimisation adaptative. Le travail est toujours en cours, mais il y a déjà quelque chose à tester.
Tout d'abord, examinons de plus prĂšs le domaine de l'optimisation des requĂȘtes.
Pourquoi le planificateur peut choisir un plan sous-optimal
La requĂȘte SQL peut ĂȘtre effectuĂ©e de diffĂ©rentes maniĂšres. Par exemple, lorsqu'il y a une jointure de deux tables, cela peut ĂȘtre fait de plusieurs maniĂšres diffĂ©rentes - en utilisant des boucles imbriquĂ©es, la fusion, le hachage. Plus il y a de tables qui participent Ă la requĂȘte, plus il y a de variations dans leurs jointures. La tĂąche du planificateur est de choisir le plan d'exĂ©cution de la requĂȘte avec le coĂ»t le plus bas parmi de nombreuses variantes.
Comme déjà mentionné, dans leur travail, les planificateurs de nombreux SGBD utilisent des informations statistiques collectées automatiquement ou manuellement. Le planificateur calcule un coût estimé sur la base de ces statistiques.
En gĂ©nĂ©ral, les planificateurs de SGBD modernes fonctionnent bien dans la plupart des situations. Cependant, dans certains cas, le plan choisi peut ĂȘtre trĂšs loin d'ĂȘtre optimal.
Par exemple,le manque d'informations statistiques à jour conduit au fait que le planificateur est guidé dans son travail par des données (trÚs probablement) incorrectes sur le nombre de lignes dans les tables jointes. Une sous-estimation (ou surestimation) excessive de la cardinalité conduit au choix de méthodes non optimales d'accÚs aux données dans les tableaux.
Une autre raison importante pourrait ĂȘtre l' absence d'index requis . En l'absence d'index, le planificateur dispose d'un choix limitĂ© de mĂ©thodes d'accĂšs aux donnĂ©es.
Utilisation de conditions dĂ©pendantes (corrĂ©lĂ©es)peut Ă©galement affecter nĂ©gativement le fonctionnement du SGBD. Le planificateur (par dĂ©faut) suppose que toutes les conditions d'une requĂȘte sont indĂ©pendantes les unes des autres, c'est-Ă -dire que la valeur d'une condition n'affecte en aucune façon l'autre. Ce n'est pas toujours le cas. Si des conditions dĂ©pendantes sont utilisĂ©es (par exemple, code postal et ville), le planificateur calculera Ă©galement le mauvais coĂ»t et la cardinalitĂ© des connexions.
Le planificateur peut Ă©galement ĂȘtre affectĂ© par l' utilisation de fonctions dans l'environnement . Une fonction pour le planificateur est une «boĂźte noire», il ne sait pas combien de lignes la fonction renverra, ce qui peut Ă©galement entraĂźner un coĂ»t de plan erronĂ©.
Façons d'influencer le travail de l'ordonnanceur
Des statistiques à jour sont une condition indispensable au bon travail de l'ordonnanceur. Tout d'abord, assurez-vous que le systÚme est configuré pour collecter réguliÚrement des informations statistiques.
Il existe plusieurs façons de remĂ©dier aux situations dĂ©crites ci-dessus et d'aider le planificateur Ă choisir les plans d'exĂ©cution de requĂȘte les plus optimaux.
Sans index, l'ordonnanceur n'a qu'une seule façon d'obtenir des donnĂ©es: l'analyse sĂ©quentielle des tables (et ce n'est pas toujours mauvais et coĂ»teux). Dans certains cas, la crĂ©ation des index nĂ©cessaires peut aider Ă accĂ©lĂ©rer l'accĂšs aux donnĂ©es - vous n'avez pas Ă analyser la table entiĂšre. Mais l'utilisation d'index (la recherche du nĂ©cessaire, la crĂ©ation, la maintenance) n'est pas gratuite. IdĂ©alement, ils devraient ĂȘtre utilisĂ©s exactement lĂ oĂč ils sont nĂ©cessaires. Et oĂč ce n'est pas nĂ©cessaire - ne l'utilisez pas.
Lorsque vous utilisez des conditions de jointure corrĂ©lĂ©es dans des requĂȘtes, vous pouvez gĂ©nĂ©rer des statistiques Ă©tendues- «dites» clairement Ă l'optimiseur que les conditions sont liĂ©es les unes aux autres. Pour ce faire, l'administrateur de base de donnĂ©es (ou le dĂ©veloppeur) doit bien connaĂźtre ses donnĂ©es et surveiller les conditions dĂ©pendantes dans les requĂȘtes, car le nombre de combinaisons de colonnes dĂ©pendantes est difficile Ă prĂ©dire Ă l'avance. Des statistiques avancĂ©es devront ĂȘtre crĂ©Ă©es manuellement pour chacune de ces options.
Lors de la création d'une fonction, vous pouvez spécifier le coût d'exécution approximatif et / ou une estimation du nombre de lignes renvoyées par la fonction. Dans la version 12 , il est devenu possible d'utiliser des fonctions d'assistance pour améliorer les estimations du planificateur basées sur des arguments. Il s'agit également d'une méthode manuelle, qui ne donne pas toujours le meilleur résultat.
Si tout le reste Ă©choue, vous pouvez rĂ©Ă©crire manuellement la demande, par exemple, en utilisant des vues matĂ©rialisĂ©es, des expressions de table communes (CTE). Ou pour clarifier les exigences du domaine et, Ă©ventuellement, rĂ©Ă©crire radicalement la logique de requĂȘte.
Et il y a une autre façon de « soupçon » le planificateur - l' optimisation des requĂȘtes d' adaptation ( une daptive q uery o ptimization). L'idĂ©e derriĂšre cette mĂ©thode est qu'aprĂšs l'exĂ©cution de la requĂȘte, les informations statistiques rĂ©elles sont stockĂ©es et, lorsque la requĂȘte donnĂ©e (ou similaire) est Ă nouveau exĂ©cutĂ©e, l'optimiseur peut s'y fier.
Le SGBD Postgres Pro Enterprise est une extension pour l'optimisation adaptative des requĂȘtes appelĂ©e AQO... Cette extension est publiĂ©e sur github: github.com/postgrespro/aqo , vous pouvez l'essayer avec vanilla PostgreSQL, plus Ă ce sujet ci-dessous.
Comment fonctionne le module
Le module AQO utilise l'apprentissage automatique dans son travail. Vous pouvez en savoir plus sur le principe de fonctionnement dans un article d'Oleg Ivanov Utilisation du machine learning pour augmenter la productivitĂ© de PostgreSQL et plus en dĂ©tail dans la prĂ©sentation Optimisation adaptative des requĂȘtes (rapport sur YouTube ).
L'essence de cette méthode est briÚvement décrite ci-dessous:
Pour estimer le coût, le planificateur a besoin d'une estimation de la cardinalité, et pour cela, à son tour, une estimation de la sélectivité des conditions est nécessaire.
Pour des conditions simples (telles que «attribut = constante» ou «attribut> constante»), l'ordonnanceur dispose d'un modÚle par lequel il estime la sélectivité. Pour ce faire, il utilise des informations statistiques: nombre de valeurs d'attributs uniques, histogrammes, etc.
Pour les conditions composées d'éléments simples utilisant des connecteurs logiques, le planificateur applique des formules facilement calculées:
- sel (pas A) = 1 - sel (A)
- sel (pas A) = 1 - sel (A)
- sel (A et B) = sel (A) * sel (B)
- sel (A ou B) = sel (pas (pas A et pas B)) = 1 - (1 - sel (A)) * (1 - sel (B))
Ces formules supposent l'indĂ©pendance (non-corrĂ©lation) des conditions A et B, en raison de laquelle nous obtenons des estimations incorrectes dans le cas oĂč cette hypothĂšse est violĂ©e.
AQO complique la formule: introduit son propre coefficient pour chaque condition simple. En utilisant l'apprentissage automatique (en utilisant la régression du plus proche voisin), AQO sélectionne ces coefficients de sorte que la sélectivité calculée par la formule corresponde le mieux à la sélectivité réelle qu'AQO a observée précédemment.
Pour cela, le module enregistre:
- , ;
- .
Dans ses travaux, l'AQO distingue les conditions jusqu'à des constantes. Cela permet de réduire la complexité du problÚme à résoudre, et d'ailleurs, dans la plupart des cas, l'information n'est toujours pas perdue: AQO ne «voit» pas la valeur de la constante, mais il «voit» la sélectivité de la condition.
La situation dans laquelle la perte se produit toujours est les conditions qui sont évaluées en tant que constante quelles que soient les valeurs spécifiques. Par exemple, pour certaines conditions, le planificateur ne peut pas faire d'estimations raisonnables et choisit une constante par défaut (par exemple, la sélectivité de la condition "expression1 = expression2" est toujours évaluée à 0,005 et "expression1> expression2" est toujours évaluée à 1/3).
Ainsi, AQO améliore l'estimation de la sélectivité des conditions complexes (et, par conséquent, l'estimation des coûts, ce qui peut conduire au choix d'un plan d'exécution plus adéquat).
Installation du module
Pour essayer les fonctionnalités du module sur PostgreSQL vanilla, vous devez utiliser un correctif spécial, puis construire le systÚme à partir des sources. Pour en savoir plus, lisez le fichier README sur github.
Si Postgres Pro Enterprise est utilisé, l'installation du module AQO se fera en mode standard:
shared_preload_libraries = 'aqo'
AprÚs cela, vous pouvez créer une extension dans la base de données requise.
Préparation de la base de données
Regardons un exemple concret du fonctionnement du module AQO dans une base de données de démonstration . Nous utiliserons une grande base de données contenant des informations sur les vols pour l'année de septembre 2016 à septembre 2017.
Commencez par créer une extension:
CREATE EXTENSION aqo;
Ensuite, dĂ©sactivez le traitement des requĂȘtes parallĂšles afin que l'affichage des plans parallĂšles ne distrait pas de la tĂąche principale:
max_parallel_workers_per_gather = 0;
pour que le planificateur PostgreSQL ait plus d'options pour joindre des tables, créez deux index:
CREATE INDEX ON flights (scheduled_departure );
CREATE INDEX ON ticket_flights (fare_conditions );
Lors de l'analyse des rĂ©sultats, nous nous concentrerons sur la valeur de BUFFERS comme le nombre de pages Ă lire pour faire le travail. Examinons Ă©galement le temps d'exĂ©cution (mais le temps sur un systĂšme chargĂ© et sur un ordinateur portable Ă la maison peut ĂȘtre trĂšs diffĂ©rent).
Augmentons le cache tampon et work_mem pour que tout le travail soit effectué en RAM:
shared_buffers = '256MB';
work_mem = '256MB';
Utilisation du module AQO
Formulons une demande: vous devez obtenir des passagers qui ont volé en classe affaires à partir d'une certaine date et qui sont arrivés avec un retard ne dépassant pas une heure.
Nous exĂ©cutons la demande sans utiliser AQO (ci-aprĂšs, une partie des informations qui nâaffectent pas la comprĂ©hension du fonctionnement du module a Ă©tĂ© supprimĂ©e des plans):
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';
Et voyons le plan rĂ©sultant: dans ce cas, le planificateur a considĂ©rĂ© le plan optimal, dans lequel, d'abord, en utilisant un scan bitmap ( ), nous obtenons un ensemble de lignes de la table des vols, que nous avons hachĂ© (un nĆud ) avec un ensemble de lignes de la table ticket_flights obtenu Ă l'aide de l'index scan ( ). Le rĂ©sultat rĂ©sultant sera utilisĂ© comme l'ensemble de lignes externe pour la jointure de boucle imbriquĂ©e finale (nĆud ). L'ensemble interne de cette jointure est obtenu Ă l'aide d'un balayage d'index exclusif de la table tickets ( ). L'opĂ©ration la plus «volumineuse» consiste Ă obtenir un ensemble de lignes interne pour la boucle imbriquĂ©e - 106 205 tampons y sont lus.
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
Ce plan peut ĂȘtre qualifiĂ© de relativement bon, car une boucle imbriquĂ©e rejoint un nombre relativement faible de lignes dans l'ensemble externe.
Maintenant, faisons une expĂ©rience et voyons comment le plan proposĂ© changera (ou ne changera pas) en fonction du changement des dates de la demande. Les dates sont sĂ©lectionnĂ©es de maniĂšre Ă augmenter sĂ©quentiellement la plage de lignes de la table Vols qui satisfont Ă la condition, ce qui conduit Ă une erreur de planification dans l'Ă©valuation de la cardinalitĂ© d'accĂšs Ă cette table. Dans le plan ci-dessus, vous pouvez voir qu'avec la premiĂšre date, l'optimiseur ne se trompe presque pas dans la cardinalitĂ© ( ). Remplaçons une Ă une les dates suivantes dans la requĂȘte:
Bitmap Heap Scan on flights f (rows=8331) (actual rows=7673)
- 01/04/2017
- 01/01/2017
- 01/08/2016
Et voyons le résultat:
Plans de requĂȘte sans 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.
Si vous collectez un bref résumé, sans utiliser le module AQO, le planificateur fonctionne comme suit:
Date | Tampons | Temps, ms | Un commentaire |
01/08/2017 | 116 831 | 675.836 | La boucle imbriquée et la jointure de hachage sont utilisées, les tables de vols et de billets sont analysées par index |
01/04/2017 | 991 756 | 2771,563 | Le mĂȘme plan, mais pas optimal. Lorsque vous choisissez l'accĂšs par index pour les tables Vols et Billets, vous pouvez voir que le planificateur fait une grosse erreur lors du calcul de la cardinalitĂ© |
01/01/2017 | 1,640,772 | 4498.741 | Le mĂȘme plan sous-optimal. Mais le planificateur dĂ©cide de faire une analyse sĂ©quentielle de la table des vols |
01/08/2016 | 60 142 | 3014.577 | Le plan a finalement changé - l'optimiseur comprend qu'il devra sélectionner un grand nombre de lignes dans les tableaux, il continue donc à analyser séquentiellement les tableaux des vols et des billets. La boucle imbriquée inefficace (dans ce cas) la remplace par une jointure de hachage. |
Plans de requĂȘte avec 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
.
Regardons à nouveau le résultat:
Date | Tampons | Temps, ms | Un commentaire |
01/08/2017 | 116 831 | 662.966 | Le plan est le mĂȘme que sans utiliser le module |
01/04/2017 | 60298 | 2218,074 | En utilisant les conseils de module, l'optimiseur comprend qu'un grand nombre de chaĂźnes sont prĂ©vues pour ĂȘtre jointes, et dĂ©jĂ Ă cette Ă©tape amĂ©liore le plan en remplaçant la boucle imbriquĂ©e par une jointure par hachage |
01/01/2017 | 60142 | 2746,485 | Le plan s'est un peu amélioré - au lieu d'accéder au bitmap de la table des vols, son scan séquentiel est utilisé |
01/08/2016 | 60142 | 3253.861 | Le plan est resté inchangé - le meilleur plan dans ce cas |
RĂ©sumer
L'utilisation du module AQO pour l'optimisation adaptative des requĂȘtes prĂ©sente Ă la fois des avantages et des inconvĂ©nients.
L'un des avantages de l'utilisation d'un module est que vous n'avez pas Ă suivre les conditions dĂ©pendantes dans les requĂȘtes. Dans certains cas, la vitesse d'exĂ©cution des requĂȘtes peut augmenter. Et il existe diffĂ©rents modes d'utilisation du module. Par exemple, vous pouvez utiliser AQO pour optimiser uniquement certains types de requĂȘtes.
Parmi les inconvénients du module, on peut distinguer des frais généraux supplémentaires pour la formation et la sauvegarde des statistiques dans les structures du module. Et les informations statistiques collectées par le module ne sont pas transférées vers des répliques.
Le module AQO n'est pas une solution miracle contre tous les problĂšmes de planification possibles. Par exemple, dans certaines situations, le module peut remplacer les statistiques Ă©tendues (si vous ne les crĂ©ez pas Ă la main), ou ne fera pas attention aux statistiques non pertinentes. Mais le module ne crĂ©era pas les index nĂ©cessaires et, de plus, ne rĂ©Ă©crira pas le texte de la requĂȘte.
Par consĂ©quent, vous ne devez pas activer le module pour toutes les demandes. Les candidats d'optimisation idĂ©aux pour AQO sont des requĂȘtes oĂč l'erreur du planificateur dans le calcul de la cardinalitĂ© des nĆuds se traduit par un mauvais plan. Et, pour une raison quelconque, il n'est pas possible d'influencer le raffinement de cette estimation.