Μετάβαση στο περιεχόμενο
Διαμόρφωση

Αναζήτηση και αντικατάσταση URL στη βάση δεδομένων του WordPress χωρίς να σπάσουν τα σειριοποιημένα δεδομένα

Άλλαξε το URL ενός site WordPress μέσα στη βάση δεδομένων χωρίς να αδειάσουν widgets, μενού ή ρυθμίσεις θέματος. Η εντολή WP-CLI που κρατά ανέπαφα τα σειριοποιημένα δεδομένα.

Δημοσιεύτηκε

Μην τρέξεις σκέτο UPDATE ... SET column = REPLACE(column, 'oldurl', 'newurl') σε μια βάση δεδομένων WordPress. Θα αλλάξει το κείμενο σωστά και ταυτόχρονα θα καταστρέψει τις ρυθμίσεις σου, επειδή το WordPress αποθηκεύει πίνακες και αντικείμενα ως σειριοποιημένες συμβολοσειρές PHP, και αυτή η μορφή καταγράφει το μήκος σε bytes για κάθε συμβολοσειρά που περιέχει. Μια ωμή αντικατάσταση σε SQL ξαναγράφει το κείμενο αλλά δεν αγγίζει ποτέ το καταγεγραμμένο μήκος, και η τιμή παύει να είναι αναγνώσιμη.

Χρησιμοποίησε αντ’ αυτού ένα εργαλείο που κατανοεί τη σειριοποίηση. Στη γραμμή εντολών αυτό είναι το wp search-replace. Κράτα πρώτα αντίγραφο της βάσης, τρέξε το ως δοκιμαστική εκτέλεση, και μετά τρέξ’ το κανονικά.

Τι σπάει στην πραγματικότητα

Μια σειριοποιημένη συμβολοσειρά PHP μοιάζει κάπως έτσι:

s:18:"http://example.com"

Το 18 είναι το μήκος σε bytes του κειμένου που ακολουθεί. Μέτρα το — το http://example.com έχει ακριβώς 18 χαρακτήρες.

Φαντάσου τώρα μια αποθηκευμένη ρύθμιση θέματος:

a:1:{s:4:"logo";s:18:"http://example.com";}

Τρέξε μια σκέτη αντικατάσταση SQL του http:// σε https:// και παίρνεις:

a:1:{s:4:"logo";s:19:"https://example.com";}

Μόνο που δεν το παίρνεις. Παίρνεις αυτό:

a:1:{s:4:"logo";s:18:"https://example.com";}

Η συμβολοσειρά είναι πλέον 19 bytes αλλά εξακολουθεί να δηλώνει 18. Η PHP διαβάζει το δηλωμένο μήκος, προχωρά 18 bytes μπροστά, δεν βρίσκει εκεί που τα περιμένει το εισαγωγικό κλεισίματος και το ερωτηματικό, και τα παρατά. Ολόκληρος ο πίνακας αποτυγχάνει να αποσειριοποιηθεί.

Γιατί αποτυγχάνει σιωπηλά

Εδώ είναι το σημείο που κάνει αυτό το σφάλμα τόσο ακριβό. Το WordPress δεν πετάει σφάλμα ούτε σταματά. Όταν διαβάζει μια επιλογή, περνά την τιμή από τον βοηθό αποσειριοποίησης, η PHP επιστρέφει false για τα κακοσχηματισμένα δεδομένα, και το WordPress δίνει στον κώδικά σου μια τιμή που συμπεριφέρεται σαν η επιλογή να μην ορίστηκε ποτέ. Το πρόσθετο ή το θέμα γυρίζει τότε στην προεπιλογή του.

Έτσι το σύμπτωμα δεν είναι σελίδα σφάλματος. Το σύμπτωμα είναι:

  • Τα widgets εξαφανίζονται από τις πλαϊνές στήλες
  • Οι ρυθμίσεις του customizer μηδενίζονται στις προεπιλογές του θέματος
  • Τα δεδομένα διάταξης ενός page builder επιστρέφουν άδεια και η σελίδα εμφανίζεται κενή
  • Οι σελίδες ρυθμίσεων των πρόσθετων μοιάζουν μόλις εγκατεστημένες
  • Τα μενού χάνουν τις θέσεις που τους είχαν ανατεθεί

Τίποτα στον πίνακα διαχείρισης δεν λέει ότι κάτι πάει στραβά. Η εγγραφή είναι ακόμη στη βάση, γεμάτη με κείμενο που μοιάζει σωστό. Μόνο το πρόθεμα του μήκους πέφτει έξω κατά ένα ή δύο bytes. Η PHP όντως εκπέμπει ειδοποίηση όταν αποτυγχάνει η αποσειριοποίηση, αλλά σε ένα παραγωγικό site με απενεργοποιημένη την εμφάνιση σφαλμάτων δεν τη βλέπει κανείς. Ενεργοποιώντας το WP_DEBUG με ενεργό WP_DEBUG_LOG και WP_DEBUG_DISPLAY σε false, αυτές οι ειδοποιήσεις καταλήγουν σε αρχείο καταγραφής όπου μπορείς να τις διαβάσεις.

Ούτε η ζημιά μοιράζεται ομοιόμορφα. Οι απλές βαθμωτές τιμές — siteurl, home, σκέτο περιεχόμενο άρθρου, ένα URL που κάθεται σε δική του στήλη — επιβιώνουν μια χαρά από μια αφελή αντικατάσταση. Σπάνε μόνο οι σειριοποιημένες τιμές. Γι’ αυτό μια κακή αντικατάσταση συχνά μοιάζει με την πρώτη ματιά ότι δούλεψε.

Έλεγξε το wp-config.php πριν αγγίξεις οτιδήποτε

Αν το wp-config.php ορίζει WP_HOME ή WP_SITEURL, αυτές οι σταθερές υπερισχύουν από ό,τι υπάρχει στη βάση. Η αλλαγή του wp_options θα φαίνεται ότι δεν κάνει απολύτως τίποτα. Ψάξε για γραμμές σαν αυτές πριν συμπεράνεις ότι το πρόβλημα είναι η βάση:

define( 'WP_HOME', 'http://example.com' );
define( 'WP_SITEURL', 'http://example.com' );

Είτε ενημέρωσέ τες με το νέο URL είτε αφαίρεσέ τες και άσε τη βάση να δίνει τον τόνο.

Κράτα πρώτα αντίγραφο, και εννοώ το

Ένα σπάσιμο σειριοποίησης δεν αναιρείται πάντα εύκολα. Μόλις το πρόθεμα μήκους και το περιεχόμενο διαφωνήσουν, η αρχική τιμή δεν ανακτάται από τη χαλασμένη — δεν μπορείς να ξέρεις ποιο ήταν το σωστό μήκος χωρίς να ξέρεις την αρχική συμβολοσειρά. Το αντίγραφο ασφαλείας είναι η μόνη πραγματική αναίρεση.

wp db export backup-before-replace.sql

Ή με mysqldump αν δεν υπάρχει WP-CLI:

mysqldump -u USER -p DBNAME > backup-before-replace.sql

Κράτα το εκτός διακομιστή αν γίνεται. Ένα αντίγραφο που κάθεται στη ρίζα του web είναι ταυτόχρονα σημείο επαναφοράς και πρόβλημα ασφαλείας.

Η ασφαλής εντολή

Το search-replace του WP-CLI αποσειριοποιεί κάθε τιμή, διατρέχει την αποκωδικοποιημένη δομή, αντικαθιστά μέσα σε αυτήν και τη σειριοποιεί ξανά με σωστά μήκη. Πρώτα δοκιμαστική εκτέλεση:

wp search-replace 'http://example.com' 'https://example.com' --dry-run --all-tables --skip-columns=guid

Διάβασε την αναφορά. Σου λέει πόσες αντικαταστάσεις θα γίνουν ανά πίνακα και στήλη. Αν η λίστα πινάκων φαίνεται λάθος, ή αν ένας πίνακας που δεν αναγνωρίζεις έχει χιλιάδες ευρήματα, σταμάτα και κοίτα πριν το τρέξεις στ’ αλήθεια.

Μετά τρέξ’ το:

wp search-replace 'http://example.com' 'https://example.com' --all-tables --skip-columns=guid --report-changed-only

Σημειώσεις για τις παραμέτρους, με ειλικρίνεια:

  • Το --all-tables καλύπτει πίνακες που δεν καταχωρεί το ίδιο το WordPress, εκεί όπου συχνά ζουν τα δεδομένα των πρόσθετων. Χωρίς αυτό, κάποιοι πίνακες πρόσθετων παραλείπονται εντελώς.
  • Το --skip-columns=guid έχει σημασία. Το guid είναι μόνιμο αναγνωριστικό για τους αναγνώστες ροών, όχι ζωντανό URL. Η επανεγγραφή του μπορεί να κάνει τους συνδρομητές της ροής να δουν όλο το αρχείο σου ως νέα άρθρα.
  • Ψάξε χωρίς την τελική κάθετο και χωρίς τις παραλλαγές πρωτοκόλλου που δεν θέλεις να αγγίξεις. Η αντικατάσταση του σκέτου example.com θα χτυπήσει και διευθύνσεις email και κάθε αναφορά μέσα σε κείμενο άρθρων.

Αν δεν μπορείς να χρησιμοποιήσεις γραμμή εντολών, τα πρόσθετα μετεγκατάστασης που κατανοούν τη σειριοποίηση κάνουν την ίδια δουλειά αποκωδικοποίησης μέσα από τον πίνακα διαχείρισης. Η συμπεριφορά που ψάχνεις περιγράφεται ρητά ως χειρισμός σειριοποιημένων δεδομένων· αν ένα εργαλείο δεν το λέει, θεώρησε ότι δεν το κάνει.

Ποιοι πίνακες και ποιες στήλες εμπλέκονται

ΠίνακαςΣτήληΚίνδυνος σειριοποίησης
wp_optionsoption_valueΥψηλός — widgets, theme mods, transients, οι περισσότερες ρυθμίσεις πρόσθετων
wp_postmetameta_valueΥψηλός — διατάξεις page builder, πίνακες προσαρμοσμένων πεδίων
wp_usermetameta_valueΥψηλός — δικαιώματα, προτιμήσεις οθόνης και πίνακα ελέγχου
wp_termmetameta_valueΜεσαίος — εξαρτάται από τα πρόσθετα
wp_postspost_content, guidΧαμηλός για το περιεχόμενο, αλλά οι παράμετροι shortcode και η σήμανση των blocks κουβαλούν URL
wp_commentscomment_content, comment_author_urlΧαμηλός
wp_linkslink_url, link_imageΧαμηλός, και συχνά αχρησιμοποίητος

Σε multisite, πρόσθεσε τους wp_blogs, wp_site, wp_sitemeta, και τους ανά site wp_2_options, wp_3_options και ούτω καθεξής. Οι αλλαγές URL σε multisite έχουν επιπλέον κανόνες πέρα από μια αναζήτηση και αντικατάσταση — αντιμετώπισέ το ως ξεχωριστή δουλειά.

Οι περιπτώσεις που το search-replace δεν λύνει πλήρως

Ας είμαστε ειλικρινείς για τα όρια:

JSON αποθηκευμένο μέσα στη βάση. Κάποιοι page builders αποθηκεύουν δεδομένα διάταξης ως JSON με τις καθέτους σε διαφυγή, οπότε το URL σου εμφανίζεται ως https:\/\/example.com και όχι ως https://example.com. Μια αναζήτηση για την κανονική μορφή θα το προσπεράσει. Ίσως χρειαστεί δεύτερο πέρασμα για τη μορφή με διαφυγή. Δοκίμασέ το σε αντίγραφο.

Τιμές κωδικοποιημένες σε Base64. Αν ένα πρόσθετο κωδικοποιεί τις ρυθμίσεις πριν τις αποθηκεύσει, καμία αντικατάσταση βασισμένη σε κείμενο δεν μπορεί καν να δει το URL. Αυτές οι ρυθμίσεις συνήθως πρέπει να ξαναμπούν από την οθόνη του ίδιου του πρόσθετου.

URL γραμμένα σκληρά μέσα σε αρχεία. Ό,τι έχει γραφτεί μέσα στο functions.php, σε πρότυπο θυγατρικού θέματος ή σε σκληρά ορισμένη διαδρομή αρχείου δεν βρίσκεται στη βάση και δεν θα πειραχτεί. Κάνε ξεχωριστά grep στον φάκελο του θέματος.

Προσωρινά αποθηκευμένη και παραγόμενη έξοδος. Η προσωρινή μνήμη αντικειμένων, η προσωρινή μνήμη σελίδων και τα αντίγραφα στο CDN συνεχίζουν να σερβίρουν τα παλιά URL ακόμη και μετά από σωστή αντικατάσταση. Καθάρισέ τα, και μετά ξαναέλεγξε πριν υποθέσεις ότι η αντικατάσταση απέτυχε.

Αν έχεις ήδη τρέξει το κακό ερώτημα

Επανάφερε το αντίγραφο ασφαλείας. Αυτή είναι η απάντηση, και αξίζει να ειπωθεί καθαρά αντί να προσφερθεί μια διαδικασία επιδιόρθωσης που ως επί το πλείστον δεν δουλεύει.

Αν δεν υπάρχει αντίγραφο, ο δρόμος ανάκτησης είναι στενός: οι τιμές που δεν ήταν ποτέ σειριοποιημένες είναι μια χαρά, ενώ οι χαλασμένες σειριοποιημένες πρέπει να ανακατασκευαστούν στο χέρι ή να μηδενιστούν αποθηκεύοντας ξανά την αντίστοιχη οθόνη ρυθμίσεων. Η εκ νέου αποθήκευση μιας περιοχής widget, η επανεπιλογή μιας θέσης μενού ή η επανεισαγωγή των ρυθμίσεων ενός πρόσθετου γράφει μια φρέσκια, σωστά σειριοποιημένη τιμή πάνω από τη χαλασμένη. Κουραστικό, αλλά δουλεύει, και είναι καλύτερο από το να αφήσεις αλλοιωμένες εγγραφές στη θέση τους, εκεί που θα μπερδέψουν τον επόμενο που θα κοιτάξει το site.

Για να παραγάγεις τις σωστές εντολές πριν τρέξεις οτιδήποτε, φτιάξ’ τες με το εργαλείο SQL αναζήτησης και αντικατάστασης για WordPress — σου δείχνει ποιες στήλες μπορούν να πειραχτούν με σκέτη SQL και ποιες απαιτούν χειρισμό που κατανοεί τη σειριοποίηση, ώστε να βλέπεις τον διαχωρισμό πριν δεσμευτείς σε μια εντολή.

FAQ

Ερωτήσεις

Γιατί ένα σκέτο REPLACE σε SQL σπάει ένα site WordPress;

Επειδή το WordPress αποθηκεύει πίνακες και αντικείμενα ως σειριοποιημένο κείμενο PHP, και αυτή η μορφή καταγράφει το μήκος σε bytes για κάθε συμβολοσειρά που περιέχει. Ένα ωμό REPLACE αλλάζει το κείμενο αλλά αφήνει τον παλιό αριθμό μήκους στη θέση του. Η PHP τότε αρνείται να αποσειριοποιήσει την τιμή, το WordPress γυρίζει σε μια προεπιλογή, και οι ρυθμίσεις εμφανίζονται άδειες αντί για λανθασμένες.

Ποιος είναι ο ασφαλέστερος τρόπος να αλλάξω το URL ενός site WordPress στη βάση δεδομένων;

Κράτα ένα πλήρες αντίγραφο ασφαλείας της βάσης, και μετά τρέξε την εντολή WP-CLI wp search-replace κάνοντας πρώτα δοκιμαστική εκτέλεση. Το WP-CLI αποσειριοποιεί κάθε τιμή, αντικαθιστά μέσα στην αποκωδικοποιημένη δομή και τη σειριοποιεί ξανά με διορθωμένα μήκη. Τα πρόσθετα μετεγκατάστασης που κατανοούν τη σειριοποίηση κάνουν την ίδια δουλειά μέσα από τον πίνακα διαχείρισης, αν δεν έχεις πρόσβαση σε γραμμή εντολών.

Ποιοι πίνακες κρατούν το παλιό URL;

Κυρίως οι wp_options, wp_posts, wp_postmeta, wp_usermeta, wp_termmeta και wp_comments. Ο κίνδυνος της σειριοποίησης βρίσκεται στον wp_options και σε κάθε στήλη meta_value, αφού τα widgets, τα theme mods, τα transients και τα δεδομένα των page builder αποθηκεύονται ως πίνακες. Σε multisite, οι wp_blogs, wp_site και wp_sitemeta κρατούν επίσης το domain.

Αν το wp-config.php ορίζει το URL, αρκεί να αλλάξω τη βάση δεδομένων;

Όχι. Αν το wp-config.php ορίζει WP_HOME ή WP_SITEURL, αυτές οι σταθερές υπερισχύουν από ό,τι κρατούν τα siteurl και home στον wp_options. Η επεξεργασία της βάσης δεν αλλάζει τίποτα ορατό μέχρι να ενημερωθούν ή να αφαιρεθούν οι σταθερές. Έλεγξε πρώτα το wp-config.php, γιατί αυτή η μία γραμμή εξηγεί πολλές αποτυχημένες μετεγκαταστάσεις.

Να αντικαταστήσω και τη στήλη guid;

Όχι. Το guid είναι μόνιμο αναγνωριστικό για τους αναγνώστες ροών, όχι λειτουργικό URL, και η επανεγγραφή του μπορεί να κάνει τους συνδρομητές της ροής να δουν ξανά ως νέα κάθε παλιό άρθρο. Πέρασε skip-columns με guid όταν τρέχεις το WP-CLI. Η μόνη συνηθισμένη εξαίρεση είναι ένα site που δεν είχε ποτέ δημόσιες ροές ή συνδρομητές.

Πώς καταλαβαίνω αν μια τιμή έχει σπάσει μετά από λάθος αντικατάσταση;

Οι χαλασμένες σειριοποιημένες τιμές φαίνονται φυσιολογικές μέσα στη βάση αλλά επιστρέφουν άδειες στο WordPress. Τα widgets εξαφανίζονται, οι ρυθμίσεις του customizer μηδενίζονται και οι επιλογές των πρόσθετων γυρίζουν στις προεπιλογές χωρίς κανένα μήνυμα σφάλματος. Ενεργοποιώντας WP_DEBUG και WP_DEBUG_LOG εμφανίζεται η ειδοποίηση αποσειριοποίησης. Η σύγκριση του δηλωμένου μήκους με τον πραγματικό αριθμό bytes το επιβεβαιώνει άμεσα.