PostgreSQL Antipatterns: skadelige JOIN og OR

Frykt for operasjoner, buffers som bringer...
La oss se pĂ„ noen universelle tilnĂŠrminger for optimalisering av spĂžrringer i PostgreSQL med et lite eksempel. Om du vil bruke dem eller ikke – det er opp til deg, men det er verdt Ă„ kjenne til dem.

I noen senere versjoner av PG kan situasjonen endre seg med en mer 'intelligent' planner, men for 9.4/9.6 ser det omtrent likt ut som eksemplene her.

Jeg tar en helt reell forespĂžrsel:

SELECT
  TRUE
FROM
  "Dokument" d
INNER JOIN
  "DokumentUtvidelse" doc_ex
    USING("@Dokument")
INNER JOIN
  "Dokumenttype" t_doc ON
    t_doc."@Dokumenttype" = d."Dokumenttype"
WHERE
  (d."LÄntaker3" = 19091 or d."Ansatt" = 19091) AND
  d."$Utkast" IS NULL AND
  d."Slettet" IS NOT TRUE AND
  doc_ex."Tilstand"[1] IS TRUE AND
  t_doc."Dokumenttype" = 'Planarbeid'
LIMIT 1;

om tabell- og felt navnMan kan se pÄ «russiske» navne pÄ felt og tabeller pÄ forskjellige mÄter, men det er en smaksak. Siden vi i «Tensor» ikke har utenlandske utviklere, og PostgreSQL lar oss gi navn selv i hieroglyfer, hvis de er innesluttet i anfÞrselstegn, sÄ foretrekker vi Ä navngi objekter klart og entydig for Ä unngÄ misforstÄelser.
La oss se pÄ den resulterende planen:
PostgreSQL Antipatterns: skadelige JOIN og OR
[se pÄ explain.tensor.ru]

144ms og nesten 53K buffers — det vil si over 400MB data! Og vi er heldige hvis alle de er i cachen pĂ„ tidspunktet for vĂ„r forespĂžrsel, ellers vil den ta flere ganger lengre tid ved lesing fra disk.

Algoritmen er viktigst!

For Ä optimalisere en hvilken som helst forespÞrsel, mÄ man fÞrst forstÄ hva den egentlig skal gjÞre.
Vi lar utviklingen av selve databasedesignet ligge utenfor rammen av denne artikkelen og blir enige om at vi kan relativt 'billig' skrive om forespÞrselen og/eller pÄfÞre databasen nÞdvendige indekser.

SĂ„, forespĂžrselen:
— sjekker for eksistensen av minst ett dokument
— i den nþdvendige tilstanden og av den spesifikke typen
— hvor forfatteren eller utfþreren er den ansatte vi trenger

JOIN + LIMIT 1

Ofte er det enklere for utvikleren Ä skrive en forespÞrsel hvor de fÞrst gjÞr en sammenkobling av mange tabeller, og deretter ender opp med én enkelt post fra den mengden. Men enklere for utvikleren betyr ikke nÞdvendigvis mer effektivt for databasen.
I vĂ„rt tilfelle var det bare 3 tabeller – men hvilken effekt det ga...

La oss fÞrst bli kvitt sammenkoblingen med tabellen "Dokumenttype", og samtidig signalisere til databasen at typen av posten er unik (det vet vi, men planleggeren har ikke fÄtt det med seg):

MED T SOM (
  SELECT
    "@Dokumenttype"
  FROM
    "Dokumenttype"
  WHERE
    "Dokumenttype" = 'Arbeidsplan'
  LIMIT 1
)
...
WHERE
  d."Dokumenttype" = (TABLE T)
...

Ja, hvis tabellen/CTE bestÄr av ett enkelt felt med ett enkelt registrering, kan man i PG skrive det slik, i stedet for

d."Dokumenttype" = (SELECT "@Dokumenttype" FROM T LIMIT 1)

«Ferdig» beregning i PostgreSQL-spÞrsmÄl

BitmapOr vs UNION

I noen tilfeller kan Bitmap Heap Scan bli veldig kostbart for oss — for eksempel i vĂ„r situasjon, hvor mange oppfĂžringer faller under det nĂždvendige kriteriet. Dette fikk vi pĂ„ grunn av OR-betingelsen, som ble til BitmapOr-operasjonen i planen.
La oss gĂ„ tilbake til den opprinnelige oppgaven — vi mĂ„ finne en oppfĂžring som samsvarer med hvilket som helst av betingelsene — det er altsĂ„ ikke nĂždvendig Ă„ sĂžke gjennom alle 59K oppfĂžringer for begge betingelsene. Det finnes en mĂ„te Ă„ hĂ„ndtere en betingelse pĂ„, og gĂ„ videre til den andre fĂžrst nĂ„r det ikke ble funnet noe pĂ„ den fĂžrste. Vi fĂ„r hjelp av en slik konstruksjon:

(
  SELECT
    ...
  LIMIT 1
)
UNION ALL
(
  SELECT
    ...
  LIMIT 1
)
LIMIT 1

«Ekstern» LIMIT 1 garanterer at sÞket stopper ved Ä finne den fÞrste oppfÞringen. Og hvis den finnes allerede i den fÞrste blokken, vil det andre ikke bli utfÞrt (never executed i planen).

«Skjule bak CASE» komplekse betingelser

I den opprinnelige forespĂžrselen er det et ekstremt ubehagelig punkt — sjekken for tilstanden i den tilknyttede tabellen «Dokumentutvidelse». Uansett sannheten i de andre betingelsene i uttrykket (for eksempel, d.«Slettet» IS NOT TRUE), blir denne koblingen alltid utfĂžrt og «koster ressurser». Mer eller mindre ressurser blir brukt — avhenger av stĂžrrelsen pĂ„ denne tabellen.
Men vi kan endre forespÞrselen slik at sÞket etter den tilknyttede oppfÞringen kun skjer nÄr det faktisk er nÞdvendig:

SELECT
  ...
FROM
  "Dokument" d
WHERE
  ... 
/*index cond*/ AND
  CASE
    WHEN "$Utkast" IS NULL AND "Slettet" IS NOT TRUE THEN (
      SELECT
        "Tilstand"[1] IS TRUE
      FROM
        "Dokumentutvidelse"
      WHERE
        "@Dokument" = d."@Dokument"
    )
  END

Siden vi ikke trenger noen av feltene fra den tilknyttede tabellen, kan vi gjĂžre JOIN om til en betingelse i en underforespĂžrsel.
La oss la indekserte felt vĂŠre «utenfor» CASE, enkle betingelser pĂ„ oppfĂžringen legger vi inn i WHEN-blokken — og nĂ„ utfĂžres «tunge» forespĂžrselen kun nĂ„r vi gĂ„r inn i THEN.

Etternavnet mitt er «Totalt»

Vi samler den resulterende forespĂžrselen med alle de ovenfor beskrevne mekanismene:

MED T SOM (
  SELECT
    "@DokumentType"
  FROM
    "DokumentType"
  WHERE
    "DokumentType" = 'Arbeidsplan'
)
  (
    SELECT
      TRUE
    FROM
      "Dokument" d
    WHERE
      ("Person3", "DokumentType") = (19091, (TABLE T)) AND
      CASE
        WHEN "$Utkast" IS NULL AND "Slettet" IS NOT TRUE THEN (
          SELECT
            "Tilstand"[1] IS TRUE
          FROM
            "DokumentUtvidelse"
          WHERE
            "@Dokument" = d."@Dokument"
        )
      END
    LIMIT 1
  )
UNION ALL
  (
    SELECT
      TRUE
    FROM
      "Dokument" d
    WHERE
      ("DokumentType", "Ansatt") = ((TABLE T), 19091) AND
      CASE
        WHEN "$Utkast" IS NULL AND "Slettet" IS NOT TRUE THEN (
          SELECT
            "Tilstand"[1] IS TRUE
          FROM
            "DokumentUtvidelse"
          WHERE
            "@Dokument" = d."@Dokument"
        )
      END
    LIMIT 1
  )
LIMIT 1;

Tilpasser [til] indekser

Det trenede Ăžyet la merke til at de indekserte forholdene i UNION-underdelene varierer litt — dette fordi vi allerede har passende indekser pĂ„ tabellen. Og hvis de ikke hadde vĂŠrt der — sĂ„ ville det vĂŠrt nĂždvendig Ă„ opprette dem: Dokument(Person3, DokumentType) og Dokument(DokumentType, Ansatt).
om rekkefÞlgen av feltene i ROW-forholdFra planleggerens synspunkt kan man selvsagt skrive ogsÄ (A, B) = (constA, constB), og (B, A) = (constB, constA). Men nÄr man skriver i rekkefÞlgen av feltene i indeksen, sÄ er en slik forespÞrsel bare lettere Ä feilsÞke senere.
Hva er planen?
PostgreSQL Antipatterns: skadelige JOIN og OR
[se pÄ explain.tensor.ru]

Dessverre fikk vi ikke hell, og i den fĂžrste UNION-blokken ble det ikke funnet noe, sĂ„ den andre ble likevel kjĂžrt. Men selv da — bare 0.037ms og 11 buffere!
Vi har akselerert forespĂžrselen og redusert "dataoverfĂžringen" i minnet flere tusen ganger, ved Ă„ bruke ganske enkle metoder — et godt resultat med lite kopiering og liming. 🙂

Kilde: habr.com

KjĂžp pĂ„litelig hosting for nettsteder med DDoS-beskyttelse, VPS VDS-servere đŸ”„ KjĂžp pĂ„litelig hosting for nettsteder med DDoS-beskyttelse, VPS VDS-servere | ProHoster