PostgreSQL Antipatterns: kahjulikud JOIN ja OR

Kartke tehingutest, mis toovad kaasa buffers...
Vaatame vĂ€ikese pĂ€ringu nĂ€itel mĂ”ned universaalsed lĂ€henemisviisid PostgreSQL pĂ€ringute optimeerimiseks. Kas neid kasutada vĂ”i mitte — otsustate teie, aga tundke need Ă€ra.

MĂ”nes hilisemates PG versioonides vĂ”ib olukord „tarkema” planeerijaga muutuda, kuid 9.4/9.6 puhul nĂ€eb see vĂ€lja enam-vĂ€hem sama, nagu siin nĂ€idatud.

VÔtan tÀiesti reaalse pÀringu:

SELECT
  TRUE
FROM
  "Dokument" d
INNER JOIN
  "DokumentRakendus" doc_ex
    USING("@Dokument")
INNER JOIN
  "DokumendiTĂŒĂŒp" t_doc ON
    t_doc."@DokumendiTĂŒĂŒp" = d."DokumendiTĂŒĂŒp"
WHERE
  (d."Isik3" = 19091 or d."Töötaja" = 19091) AND
  d."$Mustand" IS NULL AND
  d."Kustutatud" IS NOT TRUE AND
  doc_ex."Seisund"[1] IS TRUE AND
  t_doc."DokumendiTĂŒĂŒp" = 'Tööplaan'
LIMIT 1;

rÀÀkides tabelite ja vĂ€ljade nimedestVĂ”ib olla erinevaid arvamusi „vene“ nime kohta vĂ€ljadele ja tabelitele, aga see on maits kĂŒsimus. Kuna meie "Tensoris" pole vĂ€lismaalasi arendajaid, ja PostgreSQL vĂ”imaldab meil nimetada isegi hieroglĂŒĂŒfidena, kui need on tsitaatides, siis eelistame nimetada objekte ĂŒheselt arusaadavalt, et ei tekiks segadust.
Vaatame saadud plaani:
PostgreSQL Antipatterns: kahjulikud JOIN ja OR
[vaata explain.tensor.ru]

144ms ja peaaegu 53K buffers — see tĂ€hendab rohkem kui 400MB andmeid! Ja meil on Ă”nne, kui kĂ”ik need on meie pĂ€ringu hetkeks vahemĂ€llu salvestatud, vastasel juhul venib see palju kauem, kui andmed tulevad kettalt.

Algoritm on kÔige tÀhtsam!

Kuidas iganes pÀringut optimeerida, tuleb esmalt mÔista, mida see tegelikult tegema peab.
JĂ€tame selle artikli raames andmebaasi struktuuri arendamise kĂ”rvale ja lepime kokku, et saame suhteliselt 'odavalt' pĂ€ringu ĂŒmber kirjutada ja/vĂ”i rakendada andmebaasi mĂ”ningaid vajalikke indekseid.

Nii et pÀring:
— kontrollib, kas mĂ”ni dokument eksisteerib
— Ă”iges olekus ja konkreetse tĂŒĂŒbi jĂ€rgi
— kus autor vĂ”i tĂ€itja on meie vajalik töötaja

JOIN + LIMIT 1

Sageli on arendajal lihtsam kirjutada pĂ€ring, kus esmalt tehakse suure hulga tabelite ĂŒhendamine, ja siis jÀÀb sellest hulgast ainult ĂŒksainus kirje. Aga arendaja jaoks lihtsam ei tĂ€henda, et see on andmebaasi jaoks efektiivsem.
Meie juhul oli tabeleid ainult 3 — aga milline efekt...

Alustame 'DokumendiTĂŒĂŒp' tabeliga ĂŒhenduse eemaldamisega ning ĂŒtleme andmebaasile, et meie korralduskirje tĂŒĂŒp on ainulaadne (me kĂŒll teame seda, kuid planeerija ei tunne veel Ă€ra):

WITH T AS (
  SELECT
    "@DokumendiTĂŒĂŒp"
  FROM
    "DokumendiTĂŒĂŒp"
  WHERE
    "DokumendiTĂŒĂŒp" = 'Tööplaan'
  LIMIT 1
)
...
WHERE
  d."DokumendiTĂŒĂŒp" = (TABLE T)
...

Jah, kui tabel/CTE koosneb ainsast vÀljast ainsast kirjest, siis PG-s vÔib kirjutada isegi nii, selle asemel

d."DokumendiTĂŒĂŒp" = (SELECT "@DokumendiTĂŒĂŒp" FROM T LIMIT 1)

PostgreSQL pÀringutes 'laisk' arvutamine

BitmapOr vs UNION

MĂ”nes olukorras vĂ”ib Bitmap Heap Scan meile vĂ€ga kalliks minna — nĂ€iteks meie olukorras, kus piisavalt palju kirjeid langeb nĂ”utud tingimuse alla. Saime selle tĂ”ttu OR-tingimuse, mis muutus BitmapOr-operatsiooniks plaanis.
Naaseme algse ĂŒlesande juurde — peame leidma kirje, mis vastab ĂŒhele nendest tingimustest — st pole mĂ”tet otsida kĂ”iki 59K kĂ€mpingut mĂ”lema tingimuse jĂ€rgi. On viis, kuidas töötada vĂ€lja ĂŒks tingimus ja teise juurde minna ainult siis, kui esimesest ei leitud midagi.Meie abiks on selline konstruktsioon:

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

«VÀline» LIMIT 1 tagab, et otsing lÔpeb, kui esimene kirje leitakse. Ja kui see leitatakse juba esimeses blokis, siis teise tÀitmine ei toimu (never executed plaanis).

„Peidame CASE alla” keerulised tingimused

Alguses lihtsas pĂ€ringus on ÀÀrmiselt ebamugav aspekt — kontrollimine seotud tabeli „DokumentRoskelik” seisundi jĂ€rgi. ÜkskĂ”ik, kas muud tingimused vĂ€ljendis on tĂ”esed (nĂ€iteks, d.«Kustutatud» IS NOT TRUE), toimub see liitumine alati ja „kulutab ressursse”. Rohkem vĂ”i vĂ€hem neid kulutatakse, sĂ”ltub selle tabeli mahust.
Kuid pÀringu saab muuta nii, et seotud kirje otsing toimub ainult siis, kui see on tÔesti vajalik:

SELECT
  ...
FROM
  "Dokument" d
WHERE
  ... 
/*index cond*/ AND
  CASE
    WHEN "$Mustand" IS NULL AND "Kustutatud" IS NOT TRUE THEN (
      SELECT
        "Seisund"[1] IS TRUE
      FROM
        "DokumentRoskelik"
      WHERE
        "@Dokument" = d."@Dokument"
    )
  END

Kuna me ei vaja seotud tabelist ĂŒhtegi vĂ€lja , siis on meil vĂ”imalus muuta JOIN tingimuseks alampĂ€ringus.JĂ€tame indekseeritavad vĂ€ljad „kĂ”rgematesse” CASE-i, lihtsad tingimused toome sisse WHEN-blokki — ja nĂŒĂŒd „raske” pĂ€ring tĂ€idetakse ainult juhul, kui liigume THEN-i.
Minu perekonnanimi on „KokkuvĂ”te”

Kogume tulemuseks oleva pÀringu koos kÔigi eespool kirjeldatud mehhanismidega:

SOB5170

KUIDAS T ON KUIDAS (
  VALIGE
    "@DokumendiTĂŒĂŒp"
  FROM
    "DokumendiTĂŒĂŒp"
  WHERE
    "DokumendiTĂŒĂŒp" = 'Tööplaan'
)
  (
    VALIGE
      TÕSI
    FROM
      "Dokument" d
    WHERE
      ("Isik3", "DokumendiTĂŒĂŒp") = (19091, (TABEL T)) JA
      JUHUL
        KUI "$Mustandi" ON NULL JA "Kustutatud" EI OLE TÕSI SIIS (
          VALIGE
            "Seisund"[1] ON TÕSI
          FROM
            "DokumendiLaienemine"
          WHERE
            "@Dokument" = d."@Dokument"
        )
      OLE TINGIMUS
    LIMIT 1
  )
UNION ALL
  (
    VALIGE
      TÕSI
    FROM
      "Dokument" d
    WHERE
      ("DokumendiTĂŒĂŒp", "Töötaja") = ((TABEL T), 19091) JA
      JUHUL
        KUI "$Mustandi" ON NULL JA "Kustutatud" EI OLE TÕSI SIIS (
          VALIGE
            "Seisund"[1] ON TÕSI
          FROM
            "DokumendiLaienemine"
          WHERE
            "@Dokument" = d."@Dokument"
        )
      OLE TINGIMUS
    LIMIT 1
  )
LIMIT 1;

Kohandame [vastu] indekseid

Kogenud pilk mĂ€rkis, et indekseeritud tingimused UNION allĂŒksustes erinevad veidi — see on tingitud sellest, et meil on juba vastavad indeksid tabelis. Ja kui neid ei oleks — siis oleks tasunud need luua: Dokument(Isik3, DokumendiTĂŒĂŒp) ja Dokument(DokumendiTĂŒĂŒp, Töötaja).
vÀljade jÀrjekorrast ROW-tingimustesPlaneerija vaatenurgast on muidugi vÔimalik kirjutada ka (A, B) = (constA, constB), ja (B, A) = (constB, constA). Kuid kirjutamisel indeksi vÀljade jÀrjekorras, on selline pÀring lihtsalt mugavam hiljem tÔrkeotsinguks.
Mida plaanis on?
PostgreSQL Antipatterns: kahjulikud JOIN ja OR
[vaata explain.tensor.ru]

Kahjuks ei olnud meil vedu ja esimeses UNION-blokis ei leitud midagi, seega teine ikkagi lĂ€ks tĂ€itmisele. Kuid isegi sel juhul — kokku 0.037ms ja 11 puhvrit!
Me kiirendasime pĂ€ringut ja vĂ€hendasime "andmete töötlemist" mĂ€lus mitme tuhande korra, kasutades piisavalt lihtsaid meetodeid — mitte halb tulemus vĂ€ikese kopeerimise kohta. 🙂

Allikas: habr.com

Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster