Για το τι EXPLAIN είναι σιωπηλό και πώς να το κάνουμε να μιλάμε

Η κλασική ερώτηση που φέρνει ένας προγραμματιστής στο DBA του ή ένας ιδιοκτήτης επιχείρησης σε έναν σύμβουλο PostgreSQL ακούγεται σχεδόν πάντα το ίδιο: "Γιατί τα αιτήματα χρειάζονται τόσο πολύ για να ολοκληρωθούν στη βάση δεδομένων;"

Παραδοσιακό σύνολο λόγων:

  • αναποτελεσματικός αλγόριθμος
    όταν αποφασίσετε να ΣΥΜΜΕΤΕΧΕΤΕ σε πολλά CTE σε μερικές δεκάδες χιλιάδες εγγραφές
  • άσχετα στατιστικά στοιχεία
    εάν η πραγματική κατανομή των δεδομένων στον πίνακα είναι ήδη πολύ διαφορετική από αυτή που συνέλεξε η ANALYZE την προηγούμενη φορά
  • «βύσμα» στους πόρους
    και δεν υπάρχει πλέον αρκετή αποκλειστική υπολογιστική ισχύς της CPU, τα gigabyte μνήμης αντλούνται συνεχώς ή ο δίσκος δεν μπορεί να συμβαδίσει με όλα τα «θέλω» της βάσης δεδομένων
  • μπλοκάρισμα από ανταγωνιστικές διαδικασίες

Και αν τα μπλοκαρίσματα είναι αρκετά δύσκολο να πιαστούν και να αναλυθούν, τότε για οτιδήποτε άλλο χρειαζόμαστε σχέδιο ερωτήματος, το οποίο μπορεί να ληφθεί χρησιμοποιώντας ΕΞΗΓΗΣΤΕ χειριστή (Είναι καλύτερα, φυσικά, να ΕΞΗΓΗΣΕΤΕ αμέσως (ΑΝΑΛΥΣΗ, BUFFERS) ...) ή ενότητα auto_explain.

Όμως, όπως αναφέρεται στην ίδια τεκμηρίωση,

«Η κατανόηση ενός σχεδίου είναι τέχνη και για να το κατακτήσεις απαιτεί κάποια εμπειρία...»

Αλλά μπορείτε να το κάνετε χωρίς αυτό, εάν χρησιμοποιήσετε το σωστό εργαλείο!

Πώς μοιάζει συνήθως ένα σχέδιο ερωτημάτων; Κάτι τέτοιο:

Index Scan using pg_class_relname_nsp_index on pg_class (actual time=0.049..0.050 rows=1 loops=1)
  Index Cond: (relname = $1)
  Filter: (oid = $0)
  Buffers: shared hit=4
  InitPlan 1 (returns $0,$1)
    ->  Limit (actual time=0.019..0.020 rows=1 loops=1)
          Buffers: shared hit=1
          ->  Seq Scan on pg_class pg_class_1 (actual time=0.015..0.015 rows=1 loops=1)
                Filter: (relkind = 'r'::"char")
                Rows Removed by Filter: 5
                Buffers: shared hit=1

ή σαν αυτό:

"Append  (cost=868.60..878.95 rows=2 width=233) (actual time=0.024..0.144 rows=2 loops=1)"
"  Buffers: shared hit=3"
"  CTE cl"
"    ->  Seq Scan on pg_class  (cost=0.00..868.60 rows=9972 width=537) (actual time=0.016..0.042 rows=101 loops=1)"
"          Buffers: shared hit=3"
"  ->  Limit  (cost=0.00..0.10 rows=1 width=233) (actual time=0.023..0.024 rows=1 loops=1)"
"        Buffers: shared hit=1"
"        ->  CTE Scan on cl  (cost=0.00..997.20 rows=9972 width=233) (actual time=0.021..0.021 rows=1 loops=1)"
"              Buffers: shared hit=1"
"  ->  Limit  (cost=10.00..10.10 rows=1 width=233) (actual time=0.117..0.118 rows=1 loops=1)"
"        Buffers: shared hit=2"
"        ->  CTE Scan on cl cl_1  (cost=0.00..997.20 rows=9972 width=233) (actual time=0.001..0.104 rows=101 loops=1)"
"              Buffers: shared hit=2"
"Planning Time: 0.634 ms"
"Execution Time: 0.248 ms"

Αλλά η ανάγνωση του σχεδίου σε κείμενο "από το φύλλο" είναι πολύ δύσκολη και ασαφής:

  • εμφανίζεται στον κόμβο άθροισμα ανά υποδέντρο πόρους
    Δηλαδή, για να καταλάβετε πόσο χρόνο χρειάστηκε για την εκτέλεση ενός συγκεκριμένου κόμβου ή πόσο ακριβώς αυτή η ανάγνωση από τον πίνακα έφερε δεδομένα από το δίσκο, πρέπει να αφαιρέσετε με κάποιο τρόπο το ένα από το άλλο
  • απαιτείται χρόνος κόμβου πολλαπλασιάζονται με βρόχους
    ναι, η αφαίρεση δεν είναι η πιο περίπλοκη πράξη που πρέπει να γίνει "στο κεφάλι" - τελικά, ο χρόνος εκτέλεσης υποδεικνύεται ως μέσος όρος για μία εκτέλεση ενός κόμβου και μπορεί να υπάρχουν εκατοντάδες από αυτούς
  • Λοιπόν, και όλα αυτά μαζί μας εμποδίζουν να απαντήσουμε στο κύριο ερώτημα - άρα ποιος «ο πιο αδύναμος κρίκος»?

Όταν προσπαθήσαμε να τα εξηγήσουμε όλα αυτά σε αρκετές εκατοντάδες προγραμματιστές μας, συνειδητοποιήσαμε ότι από έξω έμοιαζε κάπως έτσι:

Για το τι EXPLAIN είναι σιωπηλό και πώς να το κάνουμε να μιλάμε

Και αυτό σημαίνει ότι χρειαζόμαστε...

Εργαλείο

Σε αυτό προσπαθήσαμε να συγκεντρώσουμε όλους τους βασικούς μηχανισμούς που βοηθούν να κατανοήσουμε "ποιος φταίει και τι να κάνουμε" σύμφωνα με το σχέδιο και το αίτημα. Λοιπόν, και μοιραστείτε μέρος της εμπειρίας σας με την κοινότητα.
Γνωρίστε και χρησιμοποιήστε - εξηγήστε.tensor.ru

Ορατότητα σχεδίων

Είναι εύκολο να καταλάβεις το σχέδιο όταν μοιάζει με αυτό;

Seq Scan on pg_class (actual time=0.009..1.304 rows=6609 loops=1)
  Buffers: shared hit=263
Planning Time: 0.108 ms
Execution Time: 1.800 ms

Όχι πραγματικά.

Αλλά κάπως έτσι, σε συντομευμένη μορφήΌταν διαχωρίζονται οι βασικοί δείκτες, είναι πολύ πιο ξεκάθαρο:

Για το τι EXPLAIN είναι σιωπηλό και πώς να το κάνουμε να μιλάμε

Αλλά αν το σχέδιο είναι πιο περίπλοκο, θα έρθει στη διάσωση κατανομή χρόνου piechart κατά κόμβους:

Για το τι EXPLAIN είναι σιωπηλό και πώς να το κάνουμε να μιλάμε

Λοιπόν, για τις πιο δύσκολες επιλογές βιάζεται να βοηθήσει διάγραμμα προόδου:

Για το τι EXPLAIN είναι σιωπηλό και πώς να το κάνουμε να μιλάμε

Για παράδειγμα, υπάρχουν πολύ μη τετριμμένες καταστάσεις όταν ένα σχέδιο μπορεί να έχει περισσότερες από μία πραγματικές ρίζες:

Για το τι EXPLAIN είναι σιωπηλό και πώς να το κάνουμε να μιλάμεΓια το τι EXPLAIN είναι σιωπηλό και πώς να το κάνουμε να μιλάμε

Δομικές ενδείξεις

Λοιπόν, εάν ολόκληρη η δομή του σχεδίου και τα επώδυνα σημεία του είναι ήδη σχεδιασμένα και ορατά, γιατί να μην τα επισημάνετε στον προγραμματιστή και να τα εξηγήσετε στη "ρωσική γλώσσα";

Για το τι EXPLAIN είναι σιωπηλό και πώς να το κάνουμε να μιλάμεΈχουμε ήδη συλλέξει μερικές δεκάδες τέτοια πρότυπα συστάσεων.

Προφίλ ερωτήματος γραμμή προς γραμμή

Τώρα, εάν τοποθετήσετε το αρχικό ερώτημα στο αναλυόμενο σχέδιο, μπορείτε να δείτε πόσος χρόνος ξοδεύτηκε σε κάθε μεμονωμένη δήλωση - κάπως έτσι:

Για το τι EXPLAIN είναι σιωπηλό και πώς να το κάνουμε να μιλάμε

...ή ακόμα και έτσι:

Για το τι EXPLAIN είναι σιωπηλό και πώς να το κάνουμε να μιλάμε

Αντικατάσταση παραμέτρων σε ένα αίτημα

Εάν "επισυνάψατε" όχι μόνο ένα αίτημα στο σχέδιο, αλλά και τις παραμέτρους του από τη γραμμή DETAIL του αρχείου καταγραφής, μπορείτε επιπλέον να το αντιγράψετε σε μία από τις επιλογές:

  • με αντικατάσταση τιμής στο ερώτημα
    για άμεση εκτέλεση στη βάση σας και περαιτέρω δημιουργία προφίλ

    SELECT 'const', 'param'::text;
  • με αντικατάσταση τιμής μέσω PREPARE/EXECUTE
    για εξομοίωση της εργασίας του χρονοπρογραμματιστή, όταν το παραμετρικό μέρος μπορεί να αγνοηθεί - για παράδειγμα, όταν εργάζεστε σε πίνακες με διαμερίσματα

    DEALLOCATE ALL;
    PREPARE q(text) AS SELECT 'const', $1::text;
    EXECUTE q('param'::text);
    

Αρχείο σχεδίων

Επικολλήστε, αναλύστε, μοιραστείτε με συναδέλφους! Τα σχέδια θα παραμείνουν αρχειοθετημένα και μπορείτε να επιστρέψετε σε αυτά αργότερα: εξηγήστε.tensor.ru/archive

Αλλά αν δεν θέλετε να βλέπουν άλλοι το σχέδιό σας, μην ξεχάσετε να επιλέξετε το πλαίσιο "μη δημοσίευση στο αρχείο".

Στα επόμενα άρθρα θα μιλήσω για τις δυσκολίες και τις αποφάσεις που προκύπτουν κατά την ανάλυση ενός σχεδίου.

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο