Σας προτείνω να διαβάσετε την απομαγνητοφώνηση της αναφοράς του Vladimir Sitnikov στις αρχές του 2016 "PostgreSQL και JDBC στύβουν όλο το ζουμί"
Καλό απόγευμα Το όνομά μου είναι Βλαντιμίρ Σίτνικοφ. Δουλεύω για το NetCracker εδώ και 10 χρόνια. Και είμαι κυρίως στην παραγωγικότητα. Ό,τι σχετίζεται με την Java, ό,τι σχετίζεται με την SQL είναι αυτό που αγαπώ.
Και σήμερα θα μιλήσω για αυτό που συναντήσαμε στην εταιρεία όταν ξεκινήσαμε να χρησιμοποιούμε την PostgreSQL ως διακομιστή βάσης δεδομένων. Και κυρίως δουλεύουμε με Java. Αλλά αυτό που θα σας πω σήμερα δεν αφορά μόνο την Java. Όπως έχει δείξει η πρακτική, αυτό συμβαίνει και σε άλλες γλώσσες.
Θα μιλήσουμε:
- σχετικά με τη δειγματοληψία δεδομένων.
- Σχετικά με την αποθήκευση δεδομένων.
- Και επίσης για την απόδοση.
- Και για τις υποβρύχιες τσουγκράνες που είναι θαμμένες εκεί.
Ας ξεκινήσουμε με μια απλή ερώτηση. Επιλέγουμε μία σειρά από τον πίνακα με βάση το πρωτεύον κλειδί.
Η βάση δεδομένων βρίσκεται στον ίδιο κεντρικό υπολογιστή. Και όλη αυτή η καλλιέργεια διαρκεί 20 χιλιοστά του δευτερολέπτου.
Αυτά τα 20 χιλιοστά του δευτερολέπτου είναι πολλά. Εάν έχετε 100 τέτοια αιτήματα, τότε ξοδεύετε χρόνο ανά δευτερόλεπτο κάνοντας κύλιση σε αυτά τα αιτήματα, δηλαδή χάνουμε χρόνο.
Δεν μας αρέσει να το κάνουμε αυτό και να δούμε τι μας προσφέρει η βάση για αυτό. Η βάση δεδομένων μας προσφέρει δύο επιλογές για την εκτέλεση ερωτημάτων.
Η πρώτη επιλογή είναι ένα απλό αίτημα. Τι καλό έχει; Το ότι το παίρνουμε και το στέλνουμε και τίποτα παραπάνω.
Η βάση δεδομένων έχει επίσης ένα προηγμένο ερώτημα, το οποίο είναι πιο δύσκολο, αλλά πιο λειτουργικό. Μπορείτε να στείλετε ξεχωριστά ένα αίτημα για ανάλυση, εκτέλεση, δέσμευση μεταβλητών κ.λπ.
Το εξαιρετικά εκτεταμένο ερώτημα είναι κάτι που δεν θα καλύψουμε στην τρέχουσα αναφορά. Εμείς, ίσως, θέλουμε κάτι από τη βάση δεδομένων και υπάρχει μια λίστα επιθυμιών που έχει διαμορφωθεί με κάποια μορφή, δηλαδή αυτό θέλουμε, αλλά είναι αδύνατο τώρα και τον επόμενο χρόνο. Έτσι, μόλις το ηχογραφήσαμε και θα τριγυρνάμε κουνώντας τους κύριους ανθρώπους.
Και αυτό που μπορούμε να κάνουμε είναι απλό ερώτημα και εκτεταμένο ερώτημα.
Τι είναι το ιδιαίτερο για κάθε προσέγγιση;
Ένα απλό ερώτημα είναι καλό για μία φορά εκτέλεση. Μόλις γίνει και ξεχαστεί. Και το πρόβλημα είναι ότι δεν υποστηρίζει τη μορφή δυαδικών δεδομένων, δηλαδή δεν είναι κατάλληλο για ορισμένα συστήματα υψηλής απόδοσης.
Εκτεταμένο ερώτημα – σας επιτρέπει να εξοικονομήσετε χρόνο κατά την ανάλυση. Αυτό κάναμε και αρχίσαμε να το χρησιμοποιούμε. Αυτό μας βοήθησε πραγματικά, πραγματικά. Δεν υπάρχει εξοικονόμηση μόνο στην ανάλυση. Υπάρχει εξοικονόμηση χρημάτων στη μεταφορά δεδομένων. Η μεταφορά δεδομένων σε δυαδική μορφή είναι πολύ πιο αποτελεσματική.
Ας προχωρήσουμε στην εξάσκηση. Έτσι μοιάζει μια τυπική εφαρμογή. Θα μπορούσε να είναι Java, κλπ.
Δημιουργήσαμε δήλωση. Εκτέλεσε την εντολή. Δημιουργήθηκε κοντά. Πού είναι το λάθος εδώ; Ποιο είναι το πρόβλημα? Κανένα πρόβλημα. Αυτό λέει σε όλα τα βιβλία. Έτσι πρέπει να γράφεται. Αν θέλετε μέγιστη απόδοση, γράψτε έτσι.
Αλλά η πρακτική έχει δείξει ότι αυτό δεν λειτουργεί. Γιατί; Γιατί έχουμε μέθοδο «κλειστή». Και όταν το κάνουμε αυτό, από την άποψη της βάσης δεδομένων αποδεικνύεται ότι είναι σαν ένας καπνιστής που δουλεύει με μια βάση δεδομένων. Είπαμε "ΑΝΑΛΥΣΗ ΕΚΤΕΛΕΣΤΕ ΑΠΟΣΤΟΛΗ".
Γιατί όλη αυτή η επιπλέον δημιουργία και ξεφόρτωση δηλώσεων; Κανείς δεν τους χρειάζεται. Αυτό όμως που συνήθως συμβαίνει στις PreparedStatements είναι ότι όταν τις κλείνουμε, κλείνουν τα πάντα στη βάση δεδομένων. Δεν είναι αυτό που θέλουμε.
Θέλουμε, σαν υγιείς άνθρωποι, να συνεργαστούμε με τη βάση. Πήραμε και ετοιμάσαμε τη δήλωσή μας μια φορά, μετά την εκτελούμε πολλές φορές. Στην πραγματικότητα, πολλές φορές - αυτό συμβαίνει μια φορά σε ολόκληρη τη ζωή των εφαρμογών - έχουν αναλυθεί. Και χρησιμοποιούμε το ίδιο αναγνωριστικό δήλωσης σε διαφορετικά REST. Αυτός είναι ο στόχος μας.
Πώς μπορούμε να το πετύχουμε αυτό;
Είναι πολύ απλό - δεν χρειάζεται να κλείσετε δηλώσεις. Το γράφουμε ως εξής: «προετοιμάστε» «εκτελέστε».
Αν ξεκινήσουμε κάτι τέτοιο, τότε είναι σαφές ότι κάπου κάτι θα ξεχειλίσει. Εάν δεν είναι ξεκάθαρο, μπορείτε να το δοκιμάσετε. Ας γράψουμε ένα σημείο αναφοράς που χρησιμοποιεί αυτήν την απλή μέθοδο. Δημιουργήστε μια δήλωση. Το εκκινούμε σε κάποια έκδοση του προγράμματος οδήγησης και διαπιστώνουμε ότι κολλάει αρκετά γρήγορα με την απώλεια όλης της μνήμης που είχε.
Είναι σαφές ότι τέτοια λάθη διορθώνονται εύκολα. Δεν θα μιλήσω για αυτούς. Αλλά θα πω ότι η νέα έκδοση λειτουργεί πολύ πιο γρήγορα. Η μέθοδος είναι ανόητη, αλλά ακόμα.
Πώς να εργαστείτε σωστά; Τι χρειάζεται να κάνουμε για αυτό;
Στην πραγματικότητα, οι εφαρμογές κλείνουν πάντα δηλώσεις. Σε όλα τα βιβλία λένε να το κλείσουν, αλλιώς θα διαρρεύσει η μνήμη.
Και η PostgreSQL δεν ξέρει πώς να αποθηκεύει ερωτήματα προσωρινής αποθήκευσης. Είναι απαραίτητο κάθε συνεδρία να δημιουργεί αυτήν την κρυφή μνήμη για τον εαυτό της.
Και δεν θέλουμε να χάνουμε χρόνο ούτε στην ανάλυση.
Και ως συνήθως έχουμε δύο επιλογές.
Η πρώτη επιλογή είναι να το πάρουμε και να πούμε ότι ας τυλίξουμε τα πάντα σε PgSQL. Υπάρχει μια κρυφή μνήμη εκεί. Αποθηκεύει τα πάντα. Θα βγει υπέροχο. Το είδαμε αυτό. Έχουμε 100500 αιτήματα. Δεν δουλεύει. Δεν συμφωνούμε να μετατρέψουμε τα αιτήματα σε διαδικασίες χειροκίνητα. Οχι όχι.
Έχουμε μια δεύτερη επιλογή - πάρτε το και κόψτε το μόνοι μας. Ανοίγουμε τις πηγές και αρχίζουμε να κόβουμε. Είδαμε και είδαμε. Αποδείχθηκε ότι δεν είναι τόσο δύσκολο να γίνει.
Αυτό εμφανίστηκε τον Αύγουστο του 2015. Τώρα υπάρχει μια πιο μοντέρνα έκδοση. Και όλα είναι υπέροχα. Λειτουργεί τόσο καλά που δεν αλλάζουμε τίποτα στην εφαρμογή. Και σταματήσαμε να σκεφτόμαστε προς την κατεύθυνση της PgSQL, δηλαδή αυτό ήταν αρκετό για να μειώσουμε όλα τα γενικά έξοδα σχεδόν στο μηδέν.
Αντίστοιχα, οι εντολές που έχουν προετοιμαστεί από τον διακομιστή ενεργοποιούνται την 5η εκτέλεση, προκειμένου να αποφευχθεί η σπατάλη μνήμης στη βάση δεδομένων για κάθε αίτηση μίας χρήσης.
Μπορείτε να ρωτήσετε - πού είναι οι αριθμοί; Τι παίρνετε; Και εδώ δεν θα δώσω αριθμούς, γιατί κάθε αίτημα έχει το δικό του.
Τα ερωτήματά μας ήταν τέτοια που ξοδέψαμε περίπου 20 χιλιοστά του δευτερολέπτου για ανάλυση σε ερωτήματα OLTP. Υπήρχαν 0,5 χιλιοστά του δευτερολέπτου για εκτέλεση, 20 χιλιοστά του δευτερολέπτου για ανάλυση. Αίτημα – 10 KiB κειμένου, 170 γραμμές σχεδίου. Αυτό είναι ένα αίτημα OLTP. Ζητάει 1, 5, 10 γραμμές, μερικές φορές περισσότερες.
Αλλά δεν θέλαμε να χάσουμε καθόλου 20 χιλιοστά του δευτερολέπτου. Το μειώσαμε στο 0. Όλα είναι υπέροχα.
Τι μπορείτε να αφαιρέσετε από εδώ; Αν έχεις Java, τότε παίρνεις τη σύγχρονη έκδοση του προγράμματος οδήγησης και χαίρεσαι.
Εάν μιλάτε διαφορετική γλώσσα, τότε σκεφτείτε - ίσως το χρειάζεστε και αυτό; Διότι από την άποψη της τελικής γλώσσας, για παράδειγμα, εάν έχετε PL 8 ή έχετε LibPQ, τότε δεν είναι προφανές για εσάς ότι ξοδεύετε χρόνο όχι στην εκτέλεση, στην ανάλυση, και αυτό αξίζει να το ελέγξετε. Πως? Όλα είναι δωρεάν.
Μόνο που υπάρχουν λάθη και κάποιες ιδιαιτερότητες. Και θα μιλήσουμε για αυτούς αμέσως. Το μεγαλύτερο μέρος του θα αφορά τη βιομηχανική αρχαιολογία, για το τι βρήκαμε, τι συναντήσαμε.
Εάν το αίτημα δημιουργείται δυναμικά. Συμβαίνει. Κάποιος κολλάει τις συμβολοσειρές μεταξύ τους, με αποτέλεσμα ένα ερώτημα SQL.
Γιατί είναι κακός; Είναι κακό γιατί κάθε φορά καταλήγουμε με διαφορετική χορδή.
Και ο hashCode αυτής της διαφορετικής συμβολοσειράς πρέπει να διαβαστεί ξανά. Αυτή είναι πραγματικά μια εργασία CPU - η εύρεση ενός μεγάλου κειμένου αιτήματος ακόμη και σε έναν υπάρχοντα κατακερματισμό δεν είναι τόσο εύκολη. Επομένως, το συμπέρασμα είναι απλό - μην δημιουργείτε αιτήματα. Αποθηκεύστε τα σε μία μεταβλητή. Και να χαίρεσαι.
Επόμενο πρόβλημα. Οι τύποι δεδομένων είναι σημαντικοί. Υπάρχουν ORM που λένε ότι δεν έχει σημασία τι είδους NULL υπάρχει, ας υπάρχει κάποιο είδος. Αν Int, τότε λέμε setInt. Και αν NULL, τότε ας είναι πάντα VARCHAR. Και τι διαφορά έχει τελικά τι NULL υπάρχει; Η ίδια η βάση δεδομένων θα καταλάβει τα πάντα. Και αυτή η εικόνα δεν λειτουργεί.
Στην πράξη, η βάση δεδομένων δεν ενδιαφέρεται καθόλου. Εάν είπατε την πρώτη φορά ότι πρόκειται για αριθμό και τη δεύτερη φορά είπατε ότι είναι VARCHAR, τότε είναι αδύνατο να επαναχρησιμοποιήσετε δηλώσεις που έχουν προετοιμαστεί από διακομιστή. Και σε αυτή την περίπτωση, πρέπει να ξαναδημιουργήσουμε τη δήλωσή μας.
Εάν εκτελείτε το ίδιο ερώτημα, βεβαιωθείτε ότι οι τύποι δεδομένων στη στήλη σας δεν συγχέονται. Πρέπει να προσέχετε το NULL. Αυτό είναι ένα συνηθισμένο σφάλμα που είχαμε αφότου ξεκινήσαμε να χρησιμοποιούμε το PreparedStatements
Εντάξει, ενεργοποιήθηκε. Ίσως πήραν τον οδηγό. Και η παραγωγικότητα έπεσε. Τα πράγματα έγιναν άσχημα.
Πώς συμβαίνει αυτό; Είναι αυτό ένα σφάλμα ή μια δυνατότητα; Δυστυχώς, δεν ήταν δυνατό να καταλάβουμε αν αυτό είναι σφάλμα ή δυνατότητα. Αλλά υπάρχει ένα πολύ απλό σενάριο για την αναπαραγωγή αυτού του προβλήματος. Μας έστησε ενέδρα εντελώς απροσδόκητα. Και αποτελείται από δειγματοληψία κυριολεκτικά από έναν πίνακα. Είχαμε βέβαια κι άλλα τέτοια αιτήματα. Κατά κανόνα, περιλάμβαναν δύο ή τρία τραπέζια, αλλά υπάρχει ένα τέτοιο σενάριο αναπαραγωγής. Πάρτε οποιαδήποτε έκδοση από τη βάση δεδομένων σας και παίξτε την.
Το θέμα είναι ότι έχουμε δύο στήλες, καθεμία από τις οποίες είναι ευρετηριασμένη. Υπάρχουν ένα εκατομμύριο σειρές σε μία στήλη NULL. Και η δεύτερη στήλη περιέχει μόνο 20 γραμμές. Όταν εκτελούμε χωρίς δεσμευμένες μεταβλητές, όλα λειτουργούν καλά.
Αν ξεκινήσουμε την εκτέλεση με δεσμευμένες μεταβλητές, δηλαδή εκτελέσουμε το "?" ή "1$" για το αίτημά μας, τι θα λάβουμε τελικά;
Η πρώτη εκτέλεση είναι η αναμενόμενη. Το δεύτερο είναι λίγο πιο γρήγορο. Κάτι είχε αποθηκευτεί στην κρυφή μνήμη. Τρίτο, τέταρτο, πέμπτο. Μετά μπαμ - και κάτι τέτοιο. Και το χειρότερο είναι ότι αυτό συμβαίνει στην έκτη εκτέλεση. Ποιος ήξερε ότι ήταν απαραίτητο να γίνουν ακριβώς έξι εκτελέσεις για να καταλάβει ποιο ήταν το πραγματικό σχέδιο εκτέλεσης;
Ποιος είναι ένοχος; Τι συνέβη? Η βάση δεδομένων περιέχει βελτιστοποίηση. Και φαίνεται να έχει βελτιστοποιηθεί για τη γενική περίπτωση. Και, κατά συνέπεια, ξεκινώντας από κάποιο σημείο, μεταβαίνει σε ένα γενικό σχέδιο, το οποίο, δυστυχώς, μπορεί να αποδειχθεί διαφορετικό. Μπορεί να αποδειχθεί ότι είναι το ίδιο ή μπορεί να είναι διαφορετικό. Και υπάρχει κάποιο είδος τιμής κατωφλίου που οδηγεί σε αυτή τη συμπεριφορά.
Τι μπορείτε να κάνετε για αυτό; Εδώ, βέβαια, είναι πιο δύσκολο να υποθέσουμε οτιδήποτε. Υπάρχει μια απλή λύση που χρησιμοποιούμε. Αυτό είναι +0, OFFSET 0. Σίγουρα γνωρίζετε τέτοιες λύσεις. Απλώς το παίρνουμε και προσθέτουμε "+0" στο αίτημα και όλα είναι καλά. Θα σας δείξω αργότερα.
Και υπάρχει μια άλλη επιλογή - κοιτάξτε τα σχέδια πιο προσεκτικά. Ο προγραμματιστής πρέπει όχι μόνο να γράψει ένα αίτημα, αλλά και να πει "explain analy" 6 φορές. Αν είναι 5, δεν θα λειτουργήσει.
Και υπάρχει μια τρίτη επιλογή - γράψτε μια επιστολή στους pgsql-hackers. Έγραψα, ωστόσο, δεν είναι ακόμη σαφές εάν αυτό είναι σφάλμα ή δυνατότητα.
Ενώ σκεφτόμαστε αν αυτό είναι σφάλμα ή δυνατότητα, ας το διορθώσουμε. Ας πάρουμε το αίτημά μας και ας προσθέσουμε "+0". Ολα ειναι καλά. Δύο σύμβολα και δεν χρειάζεται καν να σκεφτείς πώς είναι ή τι είναι. Πολύ απλό. Απλώς απαγορεύαμε στη βάση δεδομένων να χρησιμοποιεί ευρετήριο σε αυτήν τη στήλη. Δεν έχουμε ευρετήριο στη στήλη "+0" και αυτό είναι όλο, η βάση δεδομένων δεν χρησιμοποιεί το ευρετήριο, όλα είναι καλά.
Αυτός είναι ο κανόνας του 6 εξηγήστε. Τώρα στις τρέχουσες εκδόσεις πρέπει να το κάνετε 6 φορές αν έχετε δεσμευμένες μεταβλητές. Εάν δεν έχετε δεσμευμένες μεταβλητές, αυτό κάνουμε. Και τελικά αυτό ακριβώς το αίτημα αποτυγχάνει. Δεν είναι κάτι δύσκολο.
Φαίνεται, πόσο είναι δυνατό; Ένα bug εδώ, ένα bug εκεί. Στην πραγματικότητα, το σφάλμα είναι παντού.
Ας ρίξουμε μια πιο προσεκτική ματιά. Για παράδειγμα, έχουμε δύο σχήματα. Σχήμα Α με πίνακα S και διάγραμμα Β με πίνακα S. Ερώτημα – επιλέξτε δεδομένα από έναν πίνακα. Τι θα έχουμε σε αυτή την περίπτωση; Θα έχουμε σφάλμα. Θα έχουμε όλα τα παραπάνω. Ο κανόνας είναι - ένα σφάλμα υπάρχει παντού, θα έχουμε όλα τα παραπάνω.
Τώρα το ερώτημα είναι: «Γιατί;» Φαίνεται ότι υπάρχει τεκμηρίωση ότι εάν έχουμε ένα σχήμα, τότε υπάρχει μια μεταβλητή "search_path" που μας λέει πού να αναζητήσουμε τον πίνακα. Φαίνεται ότι υπάρχει μια μεταβλητή.
Ποιο είναι το πρόβλημα? Το πρόβλημα είναι ότι οι δηλώσεις που έχουν προετοιμαστεί από διακομιστή δεν υποψιάζονται ότι η αναζήτηση_διαδρομή μπορεί να αλλάξει από κάποιον. Αυτή η τιμή παραμένει, όπως ήταν, σταθερή για τη βάση δεδομένων. Και ορισμένα μέρη μπορεί να μην αποκτήσουν νέα σημασία.
Φυσικά, αυτό εξαρτάται από την έκδοση στην οποία δοκιμάζετε. Εξαρτάται από το πόσο σοβαρά διαφέρουν τα τραπέζια σας. Και η έκδοση 9.1 θα εκτελέσει απλώς τα παλιά ερωτήματα. Οι νέες εκδόσεις μπορεί να εντοπίσουν το σφάλμα και να σας πουν ότι έχετε ένα σφάλμα.
Πώς να το αντιμετωπίσετε; Υπάρχει μια απλή συνταγή - μην το κάνετε. Δεν χρειάζεται να αλλάξετε τη διαδρομή αναζήτησης κατά την εκτέλεση της εφαρμογής. Εάν αλλάξετε, είναι καλύτερο να δημιουργήσετε μια νέα σύνδεση.
Μπορείτε να συζητήσετε, δηλ. να ανοίξετε, να συζητήσετε, να προσθέσετε. Ίσως μπορούμε να πείσουμε τους προγραμματιστές της βάσης δεδομένων ότι όταν κάποιος αλλάζει μια τιμή, η βάση δεδομένων πρέπει να ενημερώσει τον πελάτη σχετικά με αυτό: «Κοίτα, η τιμή σας έχει ενημερωθεί εδώ. Ίσως χρειαστεί να επαναφέρετε τις δηλώσεις και να τις δημιουργήσετε ξανά;» Τώρα η βάση δεδομένων συμπεριφέρεται κρυφά και δεν αναφέρει σε καμία περίπτωση ότι οι δηλώσεις έχουν αλλάξει κάπου μέσα.
Και θα τονίσω ξανά - αυτό είναι κάτι που δεν είναι χαρακτηριστικό για την Java. Θα δούμε το ίδιο πράγμα στο PL/pgSQL ένα προς ένα. Εκεί όμως θα αναπαραχθεί.
Ας δοκιμάσουμε περισσότερες επιλογές δεδομένων. Επιλέγουμε και επιλέγουμε. Έχουμε έναν πίνακα με ένα εκατομμύριο σειρές. Κάθε γραμμή είναι ένα kilobyte. Περίπου ένα gigabyte δεδομένων. Και έχουμε μια λειτουργική μνήμη στη μηχανή Java 128 megabyte.
Εμείς, όπως συνιστάται σε όλα τα βιβλία, χρησιμοποιούμε την επεξεργασία ροής. Ανοίγουμε δηλαδή το resultSet και διαβάζουμε τα δεδομένα από εκεί σιγά σιγά. Θα λειτουργήσει; Θα πέσει από τη μνήμη; Θα διαβάσεις λίγο; Ας εμπιστευτούμε τη βάση δεδομένων, ας εμπιστευτούμε την Postgres. Δεν το πιστεύουμε. Θα πέσουμε έξω από τη μνήμη; Ποιος βίωσε το OutOfMemory; Ποιος κατάφερε να το φτιάξει μετά; Κάποιος κατάφερε να το φτιάξει.
Εάν έχετε ένα εκατομμύριο σειρές, δεν μπορείτε απλώς να διαλέξετε. Απαιτείται OFFSET/LIMIT. Ποιος είναι για αυτήν την επιλογή; Και ποιος είναι υπέρ του να παίζεις με το autoCommit;
Εδώ, ως συνήθως, η πιο απροσδόκητη επιλογή αποδεικνύεται σωστή. Και αν απενεργοποιήσετε ξαφνικά το autoCommit, θα σας βοηθήσει. Γιατί αυτό? Η επιστήμη δεν ξέρει για αυτό.
Αλλά από προεπιλογή, όλοι οι πελάτες που συνδέονται σε μια βάση δεδομένων Postgres ανακτούν ολόκληρα τα δεδομένα. Το PgJDBC δεν αποτελεί εξαίρεση από αυτή την άποψη· επιλέγει όλες τις σειρές.
Υπάρχει μια παραλλαγή στο θέμα FetchSize, δηλαδή μπορείτε να πείτε σε επίπεδο ξεχωριστής δήλωσης ότι εδώ, επιλέξτε δεδομένα κατά 10, 50. Αλλά αυτό δεν λειτουργεί μέχρι να απενεργοποιήσετε την αυτόματη δέσμευση. Απενεργοποιήθηκε η αυτόματη δέσμευση - αρχίζει να λειτουργεί.
Αλλά το να περάσετε από τον κώδικα και να ρυθμίσετε το setFetchSize παντού είναι άβολο. Επομένως, κάναμε μια ρύθμιση που θα λέει την προεπιλεγμένη τιμή για ολόκληρη τη σύνδεση.
Αυτό είπαμε. Η παράμετρος έχει ρυθμιστεί. Και τι πήραμε; Αν επιλέξουμε μικρά ποσά, αν, για παράδειγμα, επιλέξουμε 10 σειρές κάθε φορά, τότε έχουμε πολύ μεγάλα γενικά έξοδα. Επομένως, αυτή η τιμή πρέπει να οριστεί σε περίπου εκατό.
Στην ιδανική περίπτωση, φυσικά, πρέπει να μάθετε πώς να το περιορίζετε σε byte, αλλά η συνταγή είναι η εξής: ορίστε το defaultRowFetchSize σε περισσότερα από εκατό και να είστε χαρούμενοι.
Ας προχωρήσουμε στην εισαγωγή δεδομένων. Η εισαγωγή είναι ευκολότερη, υπάρχουν διαφορετικές επιλογές. Για παράδειγμα, INSERT, VALUES. Αυτή είναι μια καλή επιλογή. Μπορείτε να πείτε "INSERT SELECT". Στην πράξη είναι το ίδιο πράγμα. Δεν υπάρχει διαφορά στην απόδοση.
Τα βιβλία λένε ότι πρέπει να εκτελέσετε μια πρόταση Batch, τα βιβλία λένε ότι μπορείτε να εκτελέσετε πιο σύνθετες εντολές με πολλές παρενθέσεις. Και η Postgres έχει ένα υπέροχο χαρακτηριστικό - μπορείτε να κάνετε COPY, δηλαδή να το κάνετε πιο γρήγορα.
Αν το μετρήσετε, μπορείτε και πάλι να κάνετε μερικές ενδιαφέρουσες ανακαλύψεις. Πώς θέλουμε να λειτουργήσει αυτό; Θέλουμε να μην αναλύουμε και να μην εκτελούμε περιττές εντολές.
Στην πράξη, το TCP δεν μας επιτρέπει να το κάνουμε αυτό. Εάν ο πελάτης είναι απασχολημένος με την αποστολή ενός αιτήματος, τότε η βάση δεδομένων δεν διαβάζει τα αιτήματα σε προσπάθειες να μας στείλει απαντήσεις. Το τελικό αποτέλεσμα είναι ότι ο πελάτης περιμένει τη βάση δεδομένων να διαβάσει το αίτημα και η βάση δεδομένων περιμένει τον πελάτη να διαβάσει την απάντηση.
Και επομένως ο πελάτης αναγκάζεται να στέλνει περιοδικά ένα πακέτο συγχρονισμού. Επιπλέον αλληλεπιδράσεις δικτύου, επιπλέον χάσιμο χρόνου.
Και όσο τα προσθέτουμε τόσο χειρότερο γίνεται. Ο οδηγός είναι αρκετά απαισιόδοξος και τα προσθέτει αρκετά συχνά, περίπου μία φορά στις 200 γραμμές, ανάλογα με το μέγεθος των γραμμών κ.λπ.
Συμβαίνει να διορθώσετε μόνο μία γραμμή και όλα θα επιταχυνθούν 10 φορές. Συμβαίνει. Γιατί; Ως συνήθως, μια σταθερά όπως αυτή έχει ήδη χρησιμοποιηθεί κάπου. Και η τιμή "128" σήμαινε ότι δεν χρησιμοποιείται παρτίδα.
Είναι καλό που αυτό δεν συμπεριλήφθηκε στην επίσημη έκδοση. Ανακαλύφθηκε πριν ξεκινήσει η κυκλοφορία. Όλες οι έννοιες που δίνω βασίζονται σε σύγχρονες εκδοχές.
Ας το δοκιμάσουμε. Μετράμε το InsertBatch απλά. Μετράμε το InsertBatch πολλές φορές, δηλαδή το ίδιο πράγμα, αλλά υπάρχουν πολλές τιμές. Δύσκολη κίνηση. Δεν μπορούν όλοι να το κάνουν αυτό, αλλά είναι μια τόσο απλή κίνηση, πολύ πιο εύκολη από την ΑΝΤΙΓΡΑΦΗ.
Μπορείτε να κάνετε COPY.
Και μπορείτε να το κάνετε αυτό σε δομές. Δηλώστε τον προεπιλεγμένο τύπο χρήστη, περάστε τον πίνακα και INSERT απευθείας στον πίνακα.
Εάν ανοίξετε τον σύνδεσμο: pgjdbc/ubenchmsrk/InsertBatch.java, τότε αυτός ο κώδικας βρίσκεται στο GitHub. Μπορείτε να δείτε συγκεκριμένα ποια αιτήματα δημιουργούνται εκεί. Δεν πειράζει.
Ξεκινήσαμε. Και το πρώτο πράγμα που καταλάβαμε ήταν ότι η μη χρήση παρτίδας είναι απλά αδύνατη. Όλες οι επιλογές παρτίδας είναι μηδενικές, δηλαδή ο χρόνος εκτέλεσης είναι πρακτικά μηδενικός σε σύγκριση με μια εφάπαξ εκτέλεση.
Εισάγουμε δεδομένα. Είναι ένα πολύ απλό τραπέζι. Τρεις στήλες. Και τι βλέπουμε εδώ; Βλέπουμε ότι και οι τρεις αυτές επιλογές είναι κατά προσέγγιση συγκρίσιμες. Και το COPY είναι, φυσικά, καλύτερο.
Αυτό είναι όταν εισάγουμε κομμάτια. Όταν λέγαμε ότι μία τιμή VALUES, δύο τιμές VALUES, τρεις τιμές VALUES ή υποδεικνύαμε 10 από αυτές χωρισμένες με κόμμα. Αυτό είναι απλώς οριζόντιο τώρα. 1, 2, 4, 128. Φαίνεται ότι το ένθετο παρτίδας, το οποίο είναι σχεδιασμένο με μπλε χρώμα, τον κάνει να αισθάνεται πολύ καλύτερα. Δηλαδή, όταν εισάγετε ένα κάθε φορά ή ακόμα και όταν εισάγετε τέσσερα τη φορά, γίνεται δύο φορές καλύτερο, απλά επειδή στριμώξαμε λίγο περισσότερο στις ΑΞΙΕΣ. Λιγότερες λειτουργίες EXECUTE.
Η χρήση του COPY σε μικρούς τόμους είναι εξαιρετικά απρόβλεπτη. Δεν ζωγράφισα καν στα δύο πρώτα. Πάνε στον παράδεισο, δηλαδή αυτοί οι πράσινοι αριθμοί για ΑΝΤΙΓΡΑΦΗ.
Το COPY θα πρέπει να χρησιμοποιείται όταν έχετε τουλάχιστον εκατό σειρές δεδομένων. Το γενικό κόστος ανοίγματος αυτής της σύνδεσης είναι μεγάλο. Και, για να είμαι ειλικρινής, δεν έσκαψα προς αυτή την κατεύθυνση. Βελτιστοποίησα το Batch, αλλά όχι το COPY.
Τι κανουμε μετα? Το δοκιμάσαμε. Καταλαβαίνουμε ότι πρέπει να χρησιμοποιήσουμε είτε δομές είτε ένα έξυπνο μπακ που συνδυάζει πολλές έννοιες.
Τι πρέπει να αφαιρέσετε από τη σημερινή αναφορά;
- Το PreparedStatement είναι το παν μας. Αυτό δίνει πολλά για την παραγωγικότητα. Παράγει μια μεγάλη πτώση στην αλοιφή.
- Και πρέπει να κάνετε EXPLAIN ANALYZE 6 φορές.
- Και πρέπει να αραιώσουμε το OFFSET 0 και κόλπα όπως το +0 για να διορθώσουμε το υπόλοιπο ποσοστό των προβληματικών μας ερωτημάτων.
Πηγή: www.habr.com