Перейти до вмісту
Конфігурація

Search and replace URL у базі даних WordPress без пошкодження серіалізованих даних

Як змінити адресу сайту WordPress у базі даних і не обнулити віджети, меню та налаштування шаблону. Команда WP-CLI, яка зберігає серіалізовані дані цілими.

Опубліковано

Не запускайте звичайний UPDATE ... SET column = REPLACE(column, 'oldurl', 'newurl') по базі даних WordPress. Він коректно замінить текст і водночас знищить ваші налаштування, бо WordPress зберігає масиви та об’єкти як PHP-серіалізовані рядки, а цей формат записує довжину кожного рядка в байтах. Пряма SQL-заміна переписує текст, але записаної довжини не чіпає — і значення перестає читатися.

Замість цього беріть інструмент, який розуміє серіалізацію. У командному рядку це wp search-replace. Спершу резервна копія бази, потім пробний запуск, і лише тоді бойовий.

Що саме ламається

PHP-серіалізований рядок виглядає так:

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

18 — це довжина наступного тексту в байтах. Порахуйте — 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 байтів, але й далі стверджує, що їх 18. PHP читає задекларовану довжину, відлічує 18 байтів уперед, не знаходить там закривальних лапок і крапки з комою — і здається. Увесь масив не розсеріалізовується.

Чому воно падає мовчки

Саме через це помилка виходить такою дорогою. WordPress не викидає помилку й не зупиняється. Читаючи опцію, він проганяє значення через свій хелпер розсеріалізації, PHP повертає false для зіпсованих даних, а WordPress віддає вашому коду значення, яке поводиться так, ніби опції ніколи не існувало. Плагін чи шаблон після цього підставляє свої типові налаштування.

Тож симптом — це не сторінка з помилкою. Симптом такий:

  • Віджети зникають із бічних панелей
  • Налаштування кастомайзера скидаються до типових значень шаблону
  • Дані розкладки конструктора сторінок повертаються порожніми, і сторінка рендериться пустою
  • Сторінки налаштувань плагінів виглядають щойно встановленими
  • Меню втрачають призначені їм позиції

В адмінці ніщо не повідомляє, що щось не так. Рядок і далі в базі, і далі повний тексту, який виглядає правильним. Не збігається лише префікс довжини — на байт-два. PHP таки видає повідомлення, коли розсеріалізація не вдається, але на робочому сайті з вимкненим виводом помилок його ніхто не бачить. Якщо увімкнути WP_DEBUG разом із WP_DEBUG_LOG і встановити WP_DEBUG_DISPLAY у false, ці повідомлення потраплять до лог-файлу, де їх можна прочитати.

Шкода теж розподіляється нерівномірно. Прості скалярні значення — siteurl, home, звичайний вміст запису, адреса в окремому стовпці — переживають наївну заміну без жодних наслідків. Ламаються тільки серіалізовані значення. Тому невдала заміна на перший погляд часто виглядає успішною.

Перевірте wp-config.php, перш ніж чіпати щось інше

Якщо wp-config.php визначає WP_HOME або WP_SITEURL, ці константи перемагають усе, що є в базі. Зміни у wp_options виглядатимуть так, ніби вони взагалі нічого не зробили. Пошукайте такі рядки, перш ніж робити висновок, що проблема в базі:

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

Або оновіть їх до нової адреси, або приберіть і дайте керувати базі.

Спершу резервна копія — і це серйозно

Пошкоджену серіалізацію не завжди легко відкотити. Щойно префікс довжини й вміст розійшлися, оригінальне значення з поламаного вже не відновити — неможливо дізнатися правильну довжину, не знаючи початкового рядка. Резервна копія — єдине справжнє «скасувати».

wp db export backup-before-replace.sql

Або через mysqldump, якщо WP-CLI недоступний:

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

Тримайте копію поза сервером, якщо можете. Бекап у кореневій вебдиректорії — це одночасно точка відновлення й дірка в безпеці.

Безпечна команда

Команда 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 — постійний ідентифікатор для читалок стрічок, а не жива адреса. Його перезапис може призвести до того, що передплатники побачать увесь ваш архів як нові записи.
  • Шукайте без слеша в кінці й без тих варіантів протоколу, які ви чіпати не хочете. Заміна голого example.com зачепить ще й адреси електронної пошти та будь-яку згадку в тексті записів.

Якщо командний рядок недоступний, плагіни міграції з підтримкою серіалізації виконують ту саму роботу з розкодуванням через адмінку. Поведінка, яка вам потрібна, у їхньому описі названа прямо — робота з серіалізованими даними; якщо інструмент про це не пише, вважайте, що він цього не вміє.

Які таблиці та стовпці задіяні

ТаблицяСтовпецьРизик серіалізації
wp_optionsoption_valueВисокий — віджети, налаштування шаблону, транзієнти, більшість опцій плагінів
wp_postmetameta_valueВисокий — розкладки конструкторів сторінок, масиви довільних полів
wp_usermetameta_valueВисокий — права доступу, налаштування екранів і консолі
wp_termmetameta_valueСередній — залежить від плагінів
wp_postspost_content, guidНизький для вмісту, але атрибути шорткодів і розмітка блоків несуть адреси
wp_commentscomment_content, comment_author_urlНизький
wp_linkslink_url, link_imageНизький, і часто взагалі не використовується

У мультисайті додайте wp_blogs, wp_site, wp_sitemeta, а також посайтові wp_2_options, wp_3_options і так далі. Зміна адрес у мультисайті має додаткові правила поверх пошуку й заміни — вважайте це окремою роботою.

Випадки, які search-replace не вирішує повністю

Чесно про межі можливостей:

JSON, збережений у базі. Деякі конструктори сторінок зберігають дані розкладки як JSON з екранованими прямими слешами, тож ваша адреса виглядає як https:\/\/example.com, а не https://example.com. Пошук за звичайною формою її не знайде. Може знадобитися другий прохід по екранованому рядку. Тестуйте на копії.

Значення в кодуванні base64. Якщо плагін кодує налаштування перед збереженням, жодна текстова заміна адреси там просто не побачить. Такі налаштування зазвичай доводиться вводити заново через екран самого плагіна.

Адреси, зашиті у файлах. Усе, що записано в functions.php, шаблон дочірньої теми чи жорстко прописаний шлях до ресурсу, лежить не в базі й лишиться недоторканим. Прогоніть каталог шаблону через grep окремо.

Кеш і згенерований вивід. Об’єктний кеш, кеш сторінок і копії в CDN далі віддають старі адреси навіть після коректної заміни. Скиньте їх і перевірте ще раз, перш ніж вирішувати, що заміна не спрацювала.

Якщо ви вже запустили той поганий запит

Відновлюйте з резервної копії. Це і є відповідь, і краще сказати це прямо, ніж пропонувати процедуру ремонту, яка здебільшого не працює.

Якщо копії немає, шлях до відновлення вузький: значення, які ніколи не були серіалізовані, з ними все гаразд, а зламані серіалізовані доведеться відновлювати вручну або скидати повторним збереженням відповідного екрана налаштувань. Повторне збереження області віджетів, повторний вибір позиції меню чи повторне введення налаштувань плагіна записує зверху свіже, коректно серіалізоване значення. Марудно, але працює — і це краще, ніж лишати в базі пошкоджені рядки, які збиватимуть з пантелику наступну людину, що зазирне в цей сайт.

Щоб згенерувати правильні запити ще до того, як щось запускати, зберіть їх у інструменті search and replace SQL для WordPress — він показує, які стовпці безпечно чіпати звичайним SQL, а які потребують обробки з урахуванням серіалізації, тож ви побачите цей поділ раніше, ніж візьметеся за команду.

FAQ

Питання

Чому звичайний SQL REPLACE ламає сайт на WordPress?

Тому що WordPress зберігає масиви та об'єкти як PHP-серіалізований текст, а цей формат записує довжину кожного рядка в байтах. Прямий REPLACE змінює текст, але старе число довжини залишається на місці. PHP відмовляється розсеріалізувати таке значення, WordPress підставляє типове, і налаштування виглядають порожніми, а не помилковими.

Який найбезпечніший спосіб змінити адресу сайту WordPress у базі даних?

Зробіть повну резервну копію бази, а потім запустіть команду WP-CLI wp search-replace, спочатку в режимі пробного запуску. WP-CLI розсеріалізовує кожне значення, замінює текст усередині розкодованої структури й серіалізує назад із виправленими довжинами. Плагіни міграції, які вміють працювати з серіалізацією, роблять те саме через адмінку, якщо доступу до командного рядка немає.

У яких таблицях лежить стара адреса?

Переважно у wp_options, wp_posts, wp_postmeta, wp_usermeta, wp_termmeta і wp_comments. Небезпека серіалізації живе у wp_options та в будь-якому стовпці meta_value, бо віджети, налаштування шаблону, транзієнти й дані конструкторів сторінок зберігаються як масиви. У мультисайті домен також несуть wp_blogs, wp_site і wp_sitemeta.

Чи допоможе зміна бази даних, якщо адресу задано у wp-config.php?

Ні. Якщо wp-config.php визначає WP_HOME або WP_SITEURL, ці константи перекривають те, що лежить у siteurl і home в wp_options. Правки в базі не змінять нічого видимого, доки константи не оновлять чи не приберуть. Перевірте wp-config.php насамперед — цей один рядок пояснює чимало невдалих міграцій.

Чи потрібно замінювати й стовпець guid?

Ні. guid — це постійний ідентифікатор для читалок стрічок, а не робоча адреса, і його перезапис може призвести до того, що передплатники стрічки побачать усі старі записи як нові. Передавайте skip-columns зі значенням guid під час запуску WP-CLI. Єдиний поширений виняток — сайт, який ніколи не мав публічних стрічок і передплатників.

Як зрозуміти, що значення зламалося після невдалої заміни?

Зламані серіалізовані значення у базі виглядають нормально, але у WordPress повертаються порожніми. Віджети зникають, налаштування кастомайзера скидаються, опції плагінів повертаються до типових без жодного повідомлення про помилку. Увімкнення WP_DEBUG і WP_DEBUG_LOG виводить повідомлення про невдалу розсеріалізацію. Порівняння задекларованої довжини рядка з реальною кількістю байтів підтверджує це напряму.