Karena pekerjaan saya, saya harus menghadapi situasi ketika seorang pengembang menulis permintaan dan berpikir “Basisnya cerdas, dapat menangani semuanya sendiri!«
Dalam beberapa kasus (sebagian karena ketidaktahuan akan kemampuan database, sebagian karena optimasi prematur), pendekatan ini mengarah pada munculnya “Frankensteins”.
Pertama, saya akan memberikan contoh permintaan seperti itu:
-- для каждой ключевой пары находим ассоциированные значения полей
WITH RECURSIVE cte_bind AS (
SELECT DISTINCT ON (key_a, key_b)
key_a a
, key_b b
, fld1 bind_fld1
, fld2 bind_fld2
FROM
tbl
)
-- находим min/max значений для каждого первого ключа
, cte_max AS (
SELECT
a
, max(bind_fld1) bind_fld1
, min(bind_fld2) bind_fld2
FROM
cte_bind
GROUP BY
a
)
-- связываем по первому ключу ключевые пары и min/max-значения
, cte_a_bind AS (
SELECT
cte_bind.a
, cte_bind.b
, cte_max.bind_fld1
, cte_max.bind_fld2
FROM
cte_bind
INNER JOIN
cte_max
ON cte_max.a = cte_bind.a
)
SELECT * FROM cte_a_bind;
Untuk mengevaluasi kualitas permintaan secara substansial, mari buat beberapa kumpulan data arbitrer:
CREATE TABLE tbl AS
SELECT
(random() * 1000)::integer key_a
, (random() * 1000)::integer key_b
, (random() * 10000)::integer fld1
, (random() * 10000)::integer fld2
FROM
generate_series(1, 10000);
CREATE INDEX ON tbl(key_a, key_b);
Ternyata itu membaca data memakan waktu kurang dari seperempat waktu eksekusi kueri:
Membongkarnya sepotong demi sepotong
Mari kita lihat lebih dekat permintaan tersebut dan menjadi bingung:
- Mengapa WITH RECURSIVE ada di sini jika tidak ada CTE rekursif?
- Mengapa mengelompokkan nilai min/maks dalam CTE terpisah jika nilai tersebut kemudian dikaitkan dengan sampel asli?
+25% waktu - Mengapa menggunakan 'SELECT * FROM' tanpa syarat di akhir untuk mengulangi CTE sebelumnya?
+14% waktu
Dalam hal ini, kami sangat beruntung karena Hash Join dipilih untuk koneksi tersebut, bukan Nested Loop, karena dengan begitu kami tidak hanya menerima satu tiket CTE Scan, namun 10K!
sedikit tentang CTE ScanDi sini kita harus mengingatnya Pemindaian CTE mirip dengan Pemindaian Seq - yaitu, tidak ada pengindeksan, tetapi hanya pencarian lengkap, yang diperlukan 10K x 0.3 md = 3000ms untuk siklus dengan cte_max или 1K x 1.5 md = 1500ms saat mengulang dengan cte_bind!
Sebenarnya apa yang ingin Anda dapatkan sebagai hasilnya? Ya, biasanya ini adalah pertanyaan yang muncul pada menit ke-5 saat menganalisis kueri "tiga lantai".
Kami ingin menghasilkan keluaran untuk setiap pasangan kunci unik min/maks dari grup berdasarkan key_a.
Jadi mari kita gunakan untuk ini
SELECT DISTINCT ON(key_a, key_b)
key_a a
, key_b b
, max(fld1) OVER(w) bind_fld1
, min(fld2) OVER(w) bind_fld2
FROM
tbl
WINDOW
w AS (PARTITION BY key_a);
Karena membaca data di kedua opsi memakan waktu yang sama sekitar 4-5 ms, maka seluruh waktu kita bertambah -32% - ini dalam bentuknya yang paling murni beban dihapus dari CPU dasar, jika permintaan seperti itu dijalankan cukup sering.
Secara umum, Anda tidak boleh memaksakan alasnya untuk “membawa yang bulat, menggulung yang persegi”.
Sumber: www.habr.com