PostgreSQL Antipatterns: GABUNG dan OR yang Berbahaya

Waspadalah terhadap operasi yang membawa buffer...
Dengan menggunakan kueri kecil sebagai contoh, mari kita lihat beberapa pendekatan universal untuk mengoptimalkan kueri di PostgreSQL. Apakah Anda menggunakannya atau tidak, itu terserah Anda, tetapi ada baiknya Anda mengetahuinya.

Dalam beberapa versi PG selanjutnya situasinya mungkin berubah seiring dengan semakin cerdasnya penjadwal, namun untuk 9.4/9.6 tampilannya kira-kira sama, seperti pada contoh di sini.

Mari kita terima permintaan yang sangat nyata:

SELECT
  TRUE
FROM
  "Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚" d
INNER JOIN
  "Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅" doc_ex
    USING("@Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚")
INNER JOIN
  "Π’ΠΈΠΏΠ”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°" t_doc ON
    t_doc."@Π’ΠΈΠΏΠ”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°" = d."Π’ΠΈΠΏΠ”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°"
WHERE
  (d."Π›ΠΈΡ†ΠΎ3" = 19091 or d."Π‘ΠΎΡ‚Ρ€ΡƒΠ΄Π½ΠΈΠΊ" = 19091) AND
  d."$Π§Π΅Ρ€Π½ΠΎΠ²ΠΈΠΊ" IS NULL AND
  d."Π£Π΄Π°Π»Π΅Π½" IS NOT TRUE AND
  doc_ex."БостояниС"[1] IS TRUE AND
  t_doc."Π’ΠΈΠΏΠ”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°" = 'ΠŸΠ»Π°Π½Π Π°Π±ΠΎΡ‚'
LIMIT 1;

tentang nama tabel dan fieldNama bidang dan tabel β€œRusia” dapat diperlakukan berbeda, tetapi ini masalah selera. Karena di sini di Tensor tidak ada pengembang asing, dan PostgreSQL memungkinkan kami memberi nama bahkan dalam hieroglif, jika memang demikian diapit tanda petik, maka kita lebih memilih untuk memberi nama benda secara jelas dan jelas agar tidak terjadi kejanggalan.
Mari kita lihat rencana yang dihasilkan:
PostgreSQL Antipatterns: GABUNG dan OR yang Berbahaya
[lihat penjelasan.tensor.ru]

144ms dan hampir 53K buffer - yaitu, lebih dari 400MB data! Dan kita akan beruntung jika semuanya ada di cache pada saat permintaan kita, jika tidak maka akan memakan waktu berkali-kali lebih lama saat membaca dari disk.

Algoritmenya paling penting!

Untuk mengoptimalkan permintaan apa pun, Anda harus terlebih dahulu memahami apa yang harus dilakukannya.
Mari kita tinggalkan pengembangan struktur database itu sendiri di luar cakupan artikel ini untuk saat ini, dan setuju bahwa kita dapat melakukannya dengan relatif β€œmurah” menulis ulang permintaan tersebut dan/atau masukkan ke dasar beberapa hal yang kita perlukan Indeks.

Jadi permintaannya:
β€” memeriksa keberadaan setidaknya beberapa dokumen
- dalam kondisi yang kita perlukan dan jenis tertentu
- dimana penulis atau pelakunya adalah karyawan yang kita butuhkan

GABUNG + BATAS 1

Seringkali lebih mudah bagi pengembang untuk menulis kueri yang pertama kali menggabungkan sejumlah besar tabel, dan kemudian hanya satu catatan yang tersisa dari seluruh kumpulan ini. Namun lebih mudah bagi pengembang bukan berarti lebih efisien untuk database.
Dalam kasus kami hanya ada 3 tabel - dan apa efeknya...

Pertama-tama mari kita hilangkan koneksi dengan tabel "Jenis Dokumen", dan pada saat yang sama memberi tahu database bahwa catatan tipe kami unik (kami mengetahui hal ini, tetapi penjadwal belum mengetahuinya):

WITH T AS (
  SELECT
    "@Π’ΠΈΠΏΠ”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°"
  FROM
    "Π’ΠΈΠΏΠ”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°"
  WHERE
    "Π’ΠΈΠΏΠ”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°" = 'ΠŸΠ»Π°Π½Π Π°Π±ΠΎΡ‚'
  LIMIT 1
)
...
WHERE
  d."Π’ΠΈΠΏΠ”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°" = (TABLE T)
...

Ya, jika tabel/CTE terdiri dari satu bidang dari satu catatan, maka di PG Anda bahkan dapat menulis seperti ini, alih-alih

d."Π’ΠΈΠΏΠ”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°" = (SELECT "@Π’ΠΈΠΏΠ”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°" FROM T LIMIT 1)

Evaluasi malas dalam kueri PostgreSQL

BitmapAtau vs UNION

Dalam beberapa kasus, Pemindaian Tumpukan Bitmap akan memakan banyak biaya - misalnya, dalam situasi kita, ketika cukup banyak catatan yang memenuhi kondisi yang diperlukan. Kami mendapatkannya karena Kondisi OR berubah menjadi BitmapOr- operasi sesuai rencana.
Mari kita kembali ke masalah awal - kita perlu menemukan catatan yang sesuai untuk siapa saja dari ketentuan - yaitu, tidak perlu mencari semua 59K catatan dalam kedua kondisi. Ada cara untuk memenuhi satu syarat, dan lanjutkan ke yang kedua hanya ketika tidak ada yang ditemukan di yang pertama. Desain berikut akan membantu kami:

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

LIMIT β€œEksternal” 1 memastikan bahwa pencarian berakhir ketika catatan pertama ditemukan. Dan jika sudah ditemukan pada blok pertama maka blok kedua tidak akan dieksekusi (tidak pernah dieksekusi mengenai).

β€œMenyembunyikan kondisi sulit dalam CASE”

Ada momen yang sangat merepotkan dalam kueri awal - memeriksa status terhadap tabel terkait "DocumentExtension". Terlepas dari kebenaran kondisi lain dalam ekspresi (misalnya, d."Dihapus" TIDAK BENAR), koneksi ini selalu dijalankan dan β€œmenghabiskan sumber daya”. Lebih atau kurang dari mereka akan dibelanjakan - tergantung pada ukuran tabel ini.
Namun Anda dapat mengubah kueri sehingga pencarian rekaman terkait hanya terjadi bila benar-benar diperlukan:

SELECT
  ...
FROM
  "Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚" d
WHERE
  ... /*index cond*/ AND
  CASE
    WHEN "$Π§Π΅Ρ€Π½ΠΎΠ²ΠΈΠΊ" IS NULL AND "Π£Π΄Π°Π»Π΅Π½" IS NOT TRUE THEN (
      SELECT
        "БостояниС"[1] IS TRUE
      FROM
        "Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅"
      WHERE
        "@Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚" = d."@Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚"
    )
  END

Sekali dari tabel tertaut ke kami Π½Π΅ Π½ΡƒΠΆΠ½ΠΎ для Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Π° Π½ΠΈ ΠΎΠ΄Π½ΠΎ ΠΈΠ· ΠΏΠΎΠ»Π΅ΠΉ, maka kita mempunyai kesempatan untuk mengubah JOIN menjadi kondisi pada subquery.
Mari kita tinggalkan bidang yang diindeks "di luar tanda kurung CASE", tambahkan kondisi sederhana dari catatan ke blok WHEN - dan sekarang kueri "berat" hanya dijalankan ketika meneruskan ke THEN.

Nama belakang saya adalah "Total"

Kami mengumpulkan kueri yang dihasilkan dengan semua mekanisme yang dijelaskan di atas:

WITH T AS (
  SELECT
    "@Π’ΠΈΠΏΠ”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°"
  FROM
    "Π’ΠΈΠΏΠ”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°"
  WHERE
    "Π’ΠΈΠΏΠ”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°" = 'ΠŸΠ»Π°Π½Π Π°Π±ΠΎΡ‚'
)
  (
    SELECT
      TRUE
    FROM
      "Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚" d
    WHERE
      ("Π›ΠΈΡ†ΠΎ3", "Π’ΠΈΠΏΠ”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°") = (19091, (TABLE T)) AND
      CASE
        WHEN "$Π§Π΅Ρ€Π½ΠΎΠ²ΠΈΠΊ" IS NULL AND "Π£Π΄Π°Π»Π΅Π½" IS NOT TRUE THEN (
          SELECT
            "БостояниС"[1] IS TRUE
          FROM
            "Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅"
          WHERE
            "@Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚" = d."@Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚"
        )
      END
    LIMIT 1
  )
UNION ALL
  (
    SELECT
      TRUE
    FROM
      "Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚" d
    WHERE
      ("Π’ΠΈΠΏΠ”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°", "Π‘ΠΎΡ‚Ρ€ΡƒΠ΄Π½ΠΈΠΊ") = ((TABLE T), 19091) AND
      CASE
        WHEN "$Π§Π΅Ρ€Π½ΠΎΠ²ΠΈΠΊ" IS NULL AND "Π£Π΄Π°Π»Π΅Π½" IS NOT TRUE THEN (
          SELECT
            "БостояниС"[1] IS TRUE
          FROM
            "Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅"
          WHERE
            "@Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚" = d."@Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚"
        )
      END
    LIMIT 1
  )
LIMIT 1;

Menyesuaikan [ke] indeks

Mata yang terlatih memperhatikan bahwa kondisi indeks di subblok UNION sedikit berbeda - ini karena kami sudah memiliki indeks yang sesuai di tabel. Dan jika mereka tidak ada, ada baiknya dibuat: Dokumen (Orang3, Tipe Dokumen) ΠΈ Dokumen (Tipe Dokumen, Karyawan).
tentang urutan bidang dalam kondisi ROWΠ‘ Ρ‚ΠΎΡ‡ΠΊΠΈ зрСния ΠΏΠ»Π°Π½ΠΈΡ€ΠΎΠ²Ρ‰ΠΈΠΊΠ°, ΠΊΠΎΠ½Π΅Ρ‡Π½ΠΎ, ΠΌΠΎΠΆΠ½ΠΎ Π½Π°ΠΏΠΈΡΠ°Ρ‚ΡŒ ΠΈ (A, B) = (konstanta, konstantaB)Dan (B, A) = (konstanB, konstanta). Namun saat merekam dalam urutan bidang dalam indeks, permintaan seperti itu akan lebih mudah untuk di-debug nanti.
Apa rencananya?
PostgreSQL Antipatterns: GABUNG dan OR yang Berbahaya
[lihat penjelasan.tensor.ru]

Sayangnya kami kurang beruntung dan tidak ditemukan apa pun di blok UNION pertama, sehingga blok kedua tetap dijalankan. Namun demikian - saja 0.037ms dan 11 buffer!
Kami telah mempercepat permintaan dan mengurangi pemompaan data ke memori beberapa ribu kali, menggunakan teknik yang cukup sederhana - hasil yang bagus dengan sedikit copy-paste. πŸ™‚

Sumber: www.habr.com

Tambah komentar