Antipatterns PostgreSQL : JOIN et OR nuisibles

Méfiez-vous des opérations, des buffers apportant...
Prenons un exemple d'une petite requĂȘte pour examiner certaines approches universelles pour optimiser les requĂȘtes sur PostgreSQL. Les utiliser ou non - c'est Ă  vous de dĂ©cider, mais il est bon d'en avoir connaissance.

Dans certaines versions ultérieures de PG, la situation pourrait évoluer avec l'intelligence croissante du planificateur, mais pour 9.4/9.6, cela semble à peu prÚs identique, comme le montrent les exemples ici.

Prenons une requĂȘte tout Ă  fait rĂ©elle :

SELECT
  TRUE
FROM
  "Document" d
INNER JOIN
  "DocumentExtension" doc_ex
    USING("@Document")
INNER JOIN
  "DocumentType" t_doc ON
    t_doc."@DocumentType" = d."DocumentType"
WHERE
  (d."Person3" = 19091 or d."Employee" = 19091) AND
  d."$Draft" IS NULL AND
  d."Deleted" IS NOT TRUE AND
  doc_ex."State"[1] IS TRUE AND
  t_doc."DocumentType" = 'WorkPlan'
LIMIT 1;

sur les noms de tables et de champsOn peut avoir des opinions diffĂ©rentes sur les noms de champs et de tables « russes », mais c'est une question de goĂ»t. Étant donnĂ© que nous Ă  « Tensor » n'avons pas de dĂ©veloppeurs Ă©trangers, et PostgreSQL nous permet de donner des noms mĂȘme en idĂ©ogrammes, Ă  condition qu'ils soient entourĂ©s de guillemets, nous prĂ©fĂ©rons nommer les objets de maniĂšre claire et explicite, pour Ă©viter les ambiguĂŻtĂ©s.
Jetons un Ɠil au plan obtenu :
Antipatterns PostgreSQL : JOIN et OR nuisibles
[voir sur explain.tensor.ru]

144ms et presque 53K buffers — c'est-Ă -dire plus de 400 Mo de donnĂ©es ! Et nous aurons de la chance si tout cela se trouve dans le cache au moment de notre requĂȘte, sinon elle prendra des fois plus de temps Ă  ĂȘtre lue depuis le disque.

L'algorithme est le plus important !

Pour optimiser une requĂȘte, il faut d'abord comprendre ce qu'elle est censĂ©e faire.
Laissons pour l'instant de cĂŽtĂ© le dĂ©veloppement de la structure mĂȘme de la base de donnĂ©es, et convenons que nous pouvons relativement « facilement » réécrire la requĂȘte et/ou ajouter Ă  la base certains indices dont nous avons besoin Donc, la requĂȘte :.

— vĂ©rifie l'existence d'un document
— dans l'Ă©tat souhaitĂ© et de type spĂ©cifique
— oĂč l'auteur ou l'exĂ©cutant est l'employĂ© dont nous avons besoin
JOIN + LIMIT 1

Il est assez frĂ©quent qu'un dĂ©veloppeur prĂ©fĂšre Ă©crire une requĂȘte dans laquelle il fait d'abord des jointures entre de nombreuses tables, puis il ne reste qu'un seul enregistrement de ce grand ensemble. Mais ce qui est plus facile pour le dĂ©veloppeur n'est pas toujours plus efficace pour la base de donnĂ©es.

Dans notre cas, il n'y avait que 3 tables - et quel effet

Commençons par éliminer la jointure avec la table « DocumentType », et au passage indiquons à la base de données que

l'enregistrement de type est unique (nous le savons, mais le planificateur ne s'en doute pas encore) : (nous le savons, mais le planificateur ne le soupçonne pas encore) :

AVEC T COMME (
  SÉLECTIONNER
    "@TypeDeDocument"
  DE
    "TypeDeDocument"
  OÙ
    "TypeDeDocument" = 'PlanDeTravail'
  LIMIT 1
)
...
OÙ
  d."TypeDeDocument" = (TABLE T)
...

Oui, si la table/CTE consiste en un seul champ d'un seul enregistrement, alors dans PG, on peut mĂȘme Ă©crire ainsi, au lieu de

d."TypeDeDocument" = (SÉLECTIONNER "@TypeDeDocument" DE T LIMIT 1)

Calculs « paresseux » dans les requĂȘtes PostgreSQL

BitmapOr vs UNION

Dans certains cas, le Bitmap Heap Scan peut nous coĂ»ter trĂšs cher — par exemple ici, oĂč beaucoup d'enregistrements rĂ©pondent Ă  la condition requise. Nous avons obtenu cela Ă  cause de la condition OR, devenue BitmapOr- opĂ©ration dans le plan.
Revenons Ă  la tĂąche initiale — il faut trouver un enregistrement correspondant Ă  n'importe lequel des conditions — c'est-Ă -dire qu'il n'est pas nĂ©cessaire de chercher tous les 59K enregistrements selon les deux conditions. Il y a un moyen de traiter une condition, puis de passer Ă  la seconde seulement si rien n'a Ă©tĂ© trouvĂ© dans la premiĂšre.Nous aidera une telle construction :

(
  SÉLECTIONNER
    ...
  LIMIT 1
)
UNION ALL
(
  SÉLECTIONNER
    ...
  LIMIT 1
)
LIMIT 1

« LIMIT 1 extĂ©rieur » garantit que la recherche s'arrĂȘtera dĂšs que le premier enregistrement sera trouvĂ©. Et s'il est trouvĂ© dans le premier bloc, le second ne sera pas exĂ©cutĂ© (never executed dans le plan).

« Cacher sous CASE » des conditions complexes

Dans la requĂȘte initiale, il y a un point trĂšs gĂȘnant — la vĂ©rification de l'Ă©tat dans la table liĂ©e « DocumentExtension ». IndĂ©pendamment de la vĂ©racitĂ© des autres conditions dans l'expression (par exemple, d.«Supprimé» IS NOT TRUE), cette jointure est toujours effectuĂ©e et « consomme des ressources ». Plus ou moins, cela dĂ©pend du volume de cette table.
Mais on peut modifier la requĂȘte de sorte que la recherche de l'enregistrement liĂ© se fasse uniquement lorsque c'est rĂ©ellement nĂ©cessaire :

SÉLECTIONNER
  ...
DE
  "Document" d
OÙ
  ... 
  /*index cond*/ ET
  CASE
    QUAND "$Brouillon" EST NULL ET "Supprimé" IS NOT TRUE ALORS (
      SÉLECTIONNER
        "État"[1] IS TRUE
      DE
        "DocumentExtension"
      OÙ
        "@Document" = d."@Document"
    )
  FIN

Puisque nous n'avons besoin d'aucun des champs de la table liĂ©e pour le rĂ©sultat, nous avons la possibilitĂ© de transformer la JOIN en condition par sous-requĂȘte.
Nous laisserons les champs indexables « en dehors » du CASE, nous mettons les conditions simples dans le bloc WHEN — et maintenant, la requĂȘte « lourde » n'est exĂ©cutĂ©e que lorsqu'on entre dans le THEN.

Mon nom de famille est « Total »

Nous rassemblons la requĂȘte rĂ©sultante avec tous les mĂ©canismes dĂ©crits ci-dessus :

AVEC T COMME (
  SÉLECTIONNEZ
    "@TypeDocument"
  DE
    "TypeDocument"
  OÙ
    "TypeDocument" = 'PlanTravail'
)
  (
    SÉLECTIONNEZ
      VRAI
    À PARTIR DE
      "Document" d
    OÙ
      ("Personne3", "TypeDocument") = (19091, (TABLE T)) ET
      CAS
        QUAND "$Brouillon" EST NULL ET "Supprimé" N'EST PAS VRAI ALORS (
          SÉLECTIONNEZ
            "État"[1] EST VRAI
          DE
            "DocumentExtension"
          OÙ
            "@Document" = d."@Document"
        )
      FIN
    LIMIT 1
  )
UNION ALL
  (
    SÉLECTIONNEZ
      VRAI
    À PARTIR DE
      "Document" d
    OÙ
      ("TypeDocument", "Employé") = ((TABLE T), 19091) ET
      CAS
        QUAND "$Brouillon" EST NULL ET "Supprimé" N'EST PAS VRAI ALORS (
          SÉLECTIONNEZ
            "État"[1] EST VRAI
          DE
            "DocumentExtension"
          OÙ
            "@Document" = d."@Document"
        )
      FIN
    LIMIT 1
  )
LIMIT 1;

Adapté [à] des index

Un Ɠil averti a remarquĂ© que les conditions indexĂ©es dans les sous-blocs UNION varient lĂ©gĂšrement — c'est parce que nous avons dĂ©jĂ  des index appropriĂ©s sur la table. Et s'il n'y en avait pas — il vaudrait mieux les crĂ©er : Document(Personne3, TypeDocument) et Document(TypeDocument, EmployĂ©).
sur l'ordre des champs dans les conditions ROWDu point de vue du planificateur, bien sĂ»r, il est Ă©galement possible d'Ă©crire (A, B) = (constA, constB)et (B, A) = (constB, constA). Mais lors de l'Ă©criture dans l'ordre des champs dans l'index, cette requĂȘte est tout simplement plus facile Ă  dĂ©boguer.
Qu'est-ce au programme ?
Antipatterns PostgreSQL : JOIN et OR nuisibles
[voir sur explain.tensor.ru]

Malheureusement, nous n'avons pas eu de chance, et rien n'a Ă©tĂ© trouvĂ© dans le premier bloc UNION, donc le second a finalement Ă©tĂ© exĂ©cutĂ©. Mais mĂȘme dans ce cas — seulement 0,037 ms et 11 buffers!
Nous avons accĂ©lĂ©rĂ© la requĂȘte et rĂ©duit le "traitement" des donnĂ©es en mĂ©moire de plusieurs milliers de fois, en utilisant des mĂ©thodes assez simples — un bon rĂ©sultat avec peu de copier-coller. 🙂

Source : habr.com

Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS đŸ”„ Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster