Λόγω της δουλειάς μου, πρέπει να αντιμετωπίσω καταστάσεις όταν ένας προγραμματιστής γράφει ένα αίτημα και σκέφτεται "Η βάση είναι έξυπνη, μπορεί να χειριστεί τα πάντα μόνη της!«
Σε ορισμένες περιπτώσεις (εν μέρει από άγνοια των δυνατοτήτων της βάσης δεδομένων, εν μέρει από πρόωρες βελτιστοποιήσεις), αυτή η προσέγγιση οδηγεί στην εμφάνιση των «Φρανκενστάιν».
Αρχικά, θα δώσω ένα παράδειγμα τέτοιου αιτήματος:
-- для каждой ключевой пары находим ассоциированные значения полей
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;
Για να αξιολογήσουμε ουσιαστικά την ποιότητα ενός αιτήματος, ας δημιουργήσουμε κάποιο αυθαίρετο σύνολο δεδομένων:
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);
Τελικά φαίνεται πως η ανάγνωση των δεδομένων χρειάστηκε λιγότερο από το ένα τέταρτο του χρόνου εκτέλεση ερωτήματος:
Ξεκολλώντας το κομμάτι-κομμάτι
Ας ρίξουμε μια πιο προσεκτική ματιά στο αίτημα και ας μπερδευτούμε:
- Γιατί είναι το WITH RECURSIVE εδώ αν δεν υπάρχουν αναδρομικά CTE;
- Γιατί να ομαδοποιήσετε τις ελάχιστες/μέγιστες τιμές σε ένα ξεχωριστό CTE εάν στη συνέχεια συνδέονται με το αρχικό δείγμα ούτως ή άλλως;
+25% χρόνος - Γιατί να χρησιμοποιήσετε ένα άνευ όρων «SELECT * FROM» στο τέλος για να επαναλάβετε το προηγούμενο CTE;
+14% χρόνος
Σε αυτήν την περίπτωση, ήμασταν πολύ τυχεροί που επιλέχθηκε το Hash Join για τη σύνδεση και όχι το Nested Loop, γιατί τότε θα είχαμε λάβει όχι μόνο ένα πέρασμα σάρωσης CTE, αλλά 10K!
λίγα λόγια για τη σάρωση CTEΕδώ πρέπει να το θυμόμαστε Το CTE Scan είναι παρόμοιο με το Seq Scan - δηλαδή χωρίς ευρετηρίαση, αλλά μόνο πλήρη αναζήτηση, που θα απαιτούσε 10K x 0.3ms = 3000ms για κύκλους κατά cte_max ή 1K x 1.5ms = 1500ms κατά τον βρόχο με cte_bind!
Στην πραγματικότητα, τι θέλατε να πάρετε ως αποτέλεσμα; Ναι, συνήθως αυτή είναι η ερώτηση που τίθεται κάπου στο 5ο λεπτό της ανάλυσης ερωτημάτων «τριών ιστοριών».
Θέλαμε να κάνουμε έξοδο για κάθε μοναδικό ζεύγος κλειδιών min/max από ομάδα κατά key_a.
Ας το χρησιμοποιήσουμε λοιπόν για αυτό
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);
Δεδομένου ότι η ανάγνωση δεδομένων και στις δύο επιλογές διαρκεί τα ίδια περίπου 4-5 ms, τότε όλος ο χρόνος μας κερδίζει -32% - αυτό είναι στην πιο αγνή του μορφή αφαιρέθηκε το φορτίο από τη βασική CPU, εάν ένα τέτοιο αίτημα εκτελείται αρκετά συχνά.
Γενικά, δεν πρέπει να πιέζετε τη βάση να «κουβαλήσει το στρογγυλό, να κυλήσει το τετράγωνο».
Πηγή: www.habr.com