Search and Replace URL di Database WordPress Tanpa Merusak Data Serialized
Ganti URL situs WordPress di database tanpa membuat widget, menu, atau pengaturan tema jadi kosong. Pakai perintah WP-CLI yang menjaga data serialized tetap utuh.
Dipublikasikan
Jangan jalankan UPDATE ... SET column = REPLACE(column, 'oldurl', 'newurl') biasa di seluruh database WordPress. Perintah itu memang mengganti teksnya dengan benar, tapi sekaligus menghancurkan pengaturan Anda, karena WordPress menyimpan array dan objek sebagai string PHP-serialized, dan format itu mencatat panjang byte dari setiap string yang ada di dalamnya. Replace SQL mentah menulis ulang teksnya tapi tidak pernah menyentuh angka panjang yang tercatat, dan nilainya jadi tidak bisa dibaca lagi.
Pakai tool yang paham serialisasi. Di command line, itu artinya wp search-replace. Backup database dulu, jalankan sebagai dry run, baru jalankan sungguhan.
Apa yang sebenarnya rusak
String PHP-serialized bentuknya seperti ini:
s:18:"http://example.com"
Angka 18 adalah panjang byte dari teks setelahnya. Hitung sendiri — http://example.com tepat 18 karakter.
Sekarang bayangkan sebuah pengaturan tema yang tersimpan:
a:1:{s:4:"logo";s:18:"http://example.com";}
Jalankan replace SQL biasa dari http:// menjadi https:// dan Anda mengharapkan hasilnya:
a:1:{s:4:"logo";s:19:"https://example.com";}
Padahal bukan itu yang Anda dapat. Yang Anda dapat adalah ini:
a:1:{s:4:"logo";s:18:"https://example.com";}
String-nya sekarang 19 byte tapi masih mengaku 18. PHP membaca panjang yang dideklarasikan, berjalan 18 byte ke depan, tidak menemukan tanda kutip penutup dan titik koma di tempat yang diharapkan, lalu menyerah. Seluruh array gagal di-unserialize.
Kenapa gagalnya diam-diam
Ini bagian yang membuat bug ini mahal. WordPress tidak melempar error lalu berhenti. Saat membaca sebuah option, nilainya dilewatkan ke helper unserialize, PHP mengembalikan false untuk data yang rusak, dan WordPress menyerahkan nilai ke kode Anda seolah-olah option itu memang tidak pernah diisi. Plugin atau tema lalu jatuh ke nilai default-nya.
Jadi gejalanya bukan halaman error. Gejalanya adalah:
- Widget hilang dari sidebar
- Pengaturan Customizer kembali ke default tema
- Data layout page builder kosong dan halaman tampil polos
- Halaman pengaturan plugin tampak seperti baru diinstal
- Menu kehilangan lokasi yang sudah ditetapkan
Tidak ada satu pun keterangan di admin yang bilang ada masalah. Baris datanya masih ada di database, masih penuh dengan teks yang kelihatannya benar. Hanya prefix panjangnya yang meleset satu-dua byte. PHP memang mengeluarkan notice ketika unserialize gagal, tapi di situs produksi dengan display error dimatikan, tidak ada yang melihatnya. Mengaktifkan WP_DEBUG bersama WP_DEBUG_LOG dan WP_DEBUG_DISPLAY diset false akan menaruh notice itu di file log supaya bisa Anda baca.
Kerusakannya juga tidak merata. Nilai skalar sederhana — siteurl, home, konten post biasa, URL yang duduk di kolomnya sendiri — selamat sepenuhnya dari replace yang naif. Hanya nilai serialized yang rusak. Itulah kenapa replace yang salah sering terlihat berhasil pada pandangan pertama.
Periksa wp-config.php sebelum menyentuh apa pun
Kalau wp-config.php mendefinisikan WP_HOME atau WP_SITEURL, konstanta itu menang atas apa pun isi database. Mengubah wp_options akan terlihat sama sekali tidak berpengaruh. Cari baris seperti ini sebelum menyimpulkan bahwa masalahnya ada di database:
define( 'WP_HOME', 'http://example.com' );
define( 'WP_SITEURL', 'http://example.com' );
Perbarui ke URL baru, atau hapus saja dan biarkan database yang menentukan.
Backup dulu, dan seriuslah soal itu
Kerusakan serialisasi tidak selalu mudah dibalik. Begitu prefix panjang dan isinya tidak cocok, nilai aslinya tidak bisa dipulihkan dari nilai yang rusak — Anda tidak mungkin tahu berapa panjang yang benar tanpa tahu string aslinya. Backup adalah satu-satunya undo yang nyata.
wp db export backup-before-replace.sql
Atau dengan mysqldump kalau WP-CLI tidak tersedia:
mysqldump -u USER -p DBNAME > backup-before-replace.sql
Simpan di luar server kalau bisa. Backup yang nangkring di web root itu sekaligus titik pemulihan dan lubang keamanan.
Perintah yang aman
Perintah search-replace milik WP-CLI melakukan unserialize pada setiap nilai, menelusuri struktur yang sudah didekode, mengganti isinya di dalam struktur itu, lalu melakukan serialize ulang dengan panjang yang benar. Dry run dulu:
wp search-replace 'http://example.com' 'https://example.com' --dry-run --all-tables --skip-columns=guid
Baca laporannya. Laporan itu memberi tahu berapa penggantian yang akan terjadi per tabel dan per kolom. Kalau daftar tabelnya terlihat aneh, atau ada tabel yang tidak Anda kenali dengan ribuan kecocokan, berhenti dan periksa dulu sebelum menjalankannya sungguhan.
Lalu jalankan:
wp search-replace 'http://example.com' 'https://example.com' --all-tables --skip-columns=guid --report-changed-only
Catatan jujur soal flag-nya:
--all-tablesmencakup tabel yang tidak didaftarkan WordPress sendiri, dan di situlah data plugin sering tinggal. Tanpa flag ini, sebagian tabel plugin dilewati sepenuhnya.--skip-columns=guiditu penting.guidadalah identitas permanen untuk pembaca feed, bukan URL yang hidup. Menulis ulang isinya bisa membuat pelanggan feed melihat seluruh arsip Anda sebagai postingan baru.- Cari tanpa garis miring di akhir dan tanpa varian protokol yang tidak ingin Anda sentuh. Mengganti
example.compolos juga akan mengenai alamat email dan setiap penyebutan di dalam teks postingan.
Kalau Anda tidak bisa memakai command line, plugin migrasi yang paham serialisasi melakukan pekerjaan dekoding yang sama lewat halaman admin. Perilaku yang Anda cari disebut secara eksplisit sebagai menangani data serialized; kalau sebuah tool tidak menyebutkannya, anggap saja tidak bisa.
Tabel dan kolom mana yang terlibat
| Tabel | Kolom | Risiko serialisasi |
|---|---|---|
| wp_options | option_value | Tinggi — widget, theme mods, transient, sebagian besar pengaturan plugin |
| wp_postmeta | meta_value | Tinggi — layout page builder, array custom field |
| wp_usermeta | meta_value | Tinggi — capability, preferensi layar dan dasbor |
| wp_termmeta | meta_value | Sedang — tergantung plugin |
| wp_posts | post_content, guid | Rendah untuk konten, tapi atribut shortcode dan markup block menyimpan URL |
| wp_comments | comment_content, comment_author_url | Rendah |
| wp_links | link_url, link_image | Rendah, dan sering tidak terpakai |
Pada multisite, tambahkan wp_blogs, wp_site, wp_sitemeta, serta tabel per situs seperti wp_2_options, wp_3_options, dan seterusnya. Perubahan URL di multisite punya aturan tambahan di luar search and replace — anggap itu pekerjaan terpisah.
Kasus yang tidak sepenuhnya diselesaikan search-replace
Jujur soal batasannya:
JSON yang tersimpan di dalam database. Beberapa page builder menyimpan data layout sebagai JSON dengan garis miring yang di-escape, sehingga URL Anda muncul sebagai https:\/\/example.com bukan https://example.com. Pencarian dengan bentuk normal akan melewatkannya. Anda mungkin perlu satu pass tambahan untuk bentuk yang di-escape. Uji dulu di salinan.
Nilai yang di-encode Base64. Kalau sebuah plugin meng-encode pengaturannya sebelum menyimpan, tidak ada replace berbasis teks yang bisa melihat URL-nya sama sekali. Pengaturan seperti itu biasanya harus dimasukkan ulang lewat halaman plugin itu sendiri.
URL yang di-hardcode di dalam file. Apa pun yang ditulis di functions.php, template child theme, atau path aset yang di-hardcode tidak ada di database dan tidak akan tersentuh. Grep direktori tema secara terpisah.
Output yang di-cache dan yang di-generate. Object cache, page cache, dan salinan CDN tetap menyajikan URL lama walaupun replace-nya sudah benar. Bersihkan semuanya, lalu periksa lagi sebelum menyimpulkan replace-nya gagal.
Kalau query yang salah sudah terlanjur dijalankan
Restore backup. Itu jawabannya, dan lebih baik dikatakan terus terang daripada menawarkan prosedur perbaikan yang kebanyakan tidak berhasil.
Kalau tidak ada backup, jalur pemulihannya sempit: nilai yang tidak pernah di-serialize aman, dan nilai serialized yang rusak harus disusun ulang manual atau di-reset dengan menyimpan ulang halaman pengaturan terkait. Menyimpan ulang area widget, memilih ulang lokasi menu, atau memasukkan ulang pengaturan plugin akan menulis nilai baru yang ter-serialize dengan benar menimpa yang rusak. Melelahkan, tapi berhasil, dan itu lebih baik daripada membiarkan baris data yang korup tetap di tempatnya untuk membingungkan orang berikutnya yang memeriksa situs itu.
Untuk menyusun perintah yang benar sebelum menjalankan apa pun, buat dulu lewat tool search and replace SQL WordPress — tool itu menunjukkan kolom mana yang aman disentuh dengan SQL biasa dan mana yang butuh penanganan sadar-serialisasi, jadi Anda bisa melihat pembagiannya sebelum memutuskan perintahnya.
FAQ
Pertanyaan
Kenapa perintah SQL REPLACE biasa bisa merusak situs WordPress?
Karena WordPress menyimpan array dan objek sebagai teks PHP-serialized, dan format itu mencatat panjang byte dari setiap string di dalamnya. REPLACE mentah mengubah teksnya tapi membiarkan angka panjang yang lama. PHP lalu menolak melakukan unserialize, WordPress kembali ke nilai default, dan pengaturan tampak kosong — bukan salah, tapi hilang.
Apa cara paling aman untuk mengganti URL situs WordPress di database?
Backup penuh database dulu, lalu jalankan perintah WP-CLI wp search-replace dengan dry run terlebih dahulu. WP-CLI melakukan unserialize pada setiap nilai, mengganti isinya di dalam struktur yang sudah didekode, lalu melakukan serialize ulang dengan panjang yang sudah dikoreksi. Plugin migrasi yang paham serialisasi melakukan pekerjaan yang sama lewat halaman admin kalau Anda tidak punya akses command line.
Tabel mana saja yang menyimpan URL lama?
Terutama wp_options, wp_posts, wp_postmeta, wp_usermeta, wp_termmeta, dan wp_comments. Bahaya serialisasi ada di wp_options dan di kolom meta_value mana pun, karena widget, theme mods, transient, dan data page builder disimpan sebagai array. Pada multisite, wp_blogs, wp_site, dan wp_sitemeta juga menyimpan nama domain.
Apakah mengubah database memperbaiki URL kalau wp-config.php sudah mendefinisikannya?
Tidak. Kalau wp-config.php mendefinisikan WP_HOME atau WP_SITEURL, konstanta itu menang atas apa pun isi siteurl dan home di wp_options. Mengedit database tidak mengubah apa pun yang terlihat sampai konstanta tersebut diperbarui atau dihapus. Periksa wp-config.php lebih dulu, karena satu baris itu menjelaskan banyak migrasi yang gagal.
Apakah kolom guid juga perlu diganti?
Tidak. guid adalah identitas permanen untuk pembaca feed, bukan URL yang berfungsi, dan menulis ulang isinya bisa membuat pelanggan feed melihat semua postingan lama sebagai postingan baru. Gunakan skip-columns dengan guid saat menjalankan WP-CLI. Pengecualian yang umum hanya situs yang memang tidak pernah punya feed publik atau pelanggan.
Bagaimana cara tahu sebuah nilai rusak setelah replace yang salah?
Nilai serialized yang rusak terlihat normal di database tapi kembali kosong di WordPress. Widget hilang, pengaturan customizer tema ter-reset, dan opsi plugin balik ke default tanpa pesan error apa pun. Mengaktifkan WP_DEBUG dan WP_DEBUG_LOG akan memunculkan notice unserialize. Membandingkan panjang string yang dideklarasikan dengan jumlah byte sebenarnya memastikannya secara langsung.