Восстановление ключей в таблицах WordPress

  • 19.07.15
  • 12:14
  • 10370
  • 14

Дан ресурс на WordPress, у таблиц которого отсутствует автоинкремент и первичный ключ. Как так получилось — неизвестно, предполагать не буду. Ресурс проработал в таком состоянии почти месяц, в течение которого пользователи не могли регистрироваться, невозможно было создавать новые посты, наблюдались проблемы с комментированием.

Симптомы описаны здесь.

На время починки ресурса его следует запереть при помощи .htaccess, размещённом в корне сайта. Для apache 2.4 в .htaccess следует добавить строку

и закомментировать строку "Require all granted", если таковая имеется.

Для apache 2.2 и более старых версий .htaccess должен содержать

Устанавливать и/или активировать плагины и изменять их настройки при таком положении дел не безопасно: вся работа с ними опирается на базу данных. Её использование в этом состоянии может усугубить ситуацию, создав множество некорректных записей, вычленить каждую из которых до возникновения из-за неё какой-либо проблемы будет невозможно.

Хотя в текущей версии WordPress (4.2.2) изменение опций в принципе допустимо — потому что работа с ними ведётся по именам, а не по идентификаторам — стабильность гарантировать нельзя.

Все изменения в базу данных следует вносить только после отработки их на некоторой экспериментальной БД. Каждая отработанная команда добавляется в SQL-скрипт, который затем, когда все экспериментальные таблицы будут налажены, просто запускается в БД боевого сервера. Это существенно снижает риск окончательной поломки ресурса.

Перед началом все восстанавливаемые таблицы следует задампить — как для копирования в экспериментальную базу и, возможно, её неоднократного восстановления, так и на случай неудачной починки, проблемы из-за которой могут проявиться не сразу. В самом простом случае дампу подлежат следующие 12 таблиц:

  • wp_commentmeta
  • wp_comments
  • wp_links
  • wp_options
  • wp_postmeta
  • wp_posts
  • wp_term_relationships
  • wp_term_taxonomy
  • wp_termmeta (с WP 4.4)
  • wp_terms
  • wp_usermeta
  • wp_users

В качестве префикса таблиц взят стандартный — 'wp_'; его следует заменить тем, что задан в wp-config.php в переменной $table_prefix. В зависимости от используемых плагинов таблиц может быть больше.

У мультисайтов имеются дополнительные таблицы:

  • таблицы самой сети — wp_blogs, wp_blog_versions, wp_registration_log, wp_site, wp_sitemeta, wp_signups;
  • таблицы каждого сайта («блога») этой сети, отличающиеся префиксом — wp_2_, wp_3_ и т.д.

Здесь рассматривается одинарный сайт. Мультисайт чинится аналогично. Существенное отличие заключается только в генерации запросов для дочерних блогов: для каждого из них надо взять запросы для починки одинарного сайта и в именах таблиц поменять префиксы. Это можно реализовать на любом скриптовом языке.

Определения здоровых таблиц находятся в /wp-admin/includes/schema.php той версии WordPress, что используется проблемным ресурсом. Инструкцию, которой может быть воссоздана уже существующая таблица, можно получить запросом 'SHOW CREATE TABLE':

Поле идентификаторов во всех корректных таблицах WordPress задано одинаково:

  • это первое поле таблицы;
  • оно определено как bigint(20) unsigned NOT NULL AUTO_INCREMENT;
  • оно является первичным ключом.

Исключение составляет только wp_term_relationships — идентификатором её записи не обладают, а в качестве первичного ключа выступает пара (object_id, term_taxonomy_id).

У мультисайтов есть ещё одно исключение — wp_blog_versions, идентификаторы блогов в которой являются ключом, но не имеют автоинкремента, т.к. формируются при создании записей в wp_blogs.

Для определения корректности формата таблицы также можно использовать DESCRIBE:

Для примера, нездоровая таблица wp_options выглядела так:

После изучения таблиц с помощью SHOW CREATE TABLE получился такой список проблем:

таблица ключ нет автоинкремента нет первичного ключа
wp_users ID X X
wp_usermeta umeta_id X
wp_terms term_id X
wp_term_taxonomy term_taxonomy_id X
wp_posts ID X X
wp_postmeta meta_id X
wp_comments comment_ID X X
wp_commentmeta meta_id X
wp_options option_id X X
wp_links link_id X
Таблица опций

Значение полей с автоинкрементом всегда должны быть положительны. Отсутствие автоинкремента приводит ко множеству нулевых идентификаторов:

На данный момент WordPress при работе с опциями опирается на option_name, а от идентификаторов требуется только уникальность, поэтому все нули в option_id можно заполнить автоинкрементом. Чтобы нули заменялись автоматически, следует убедиться, что параметр @@sql_mode не содержит NO_AUTO_VALUE_ON_ZERO:

Если возникают коллизии, то перенумерацию можно провести обходным путём.

Замечание: при работе с phpMyAdmin следует учитывать, что каждый набор запросов выполняется в собственной сессии. Поэтому если @@sql_mode содержит NO_AUTO_VALUE_ON_ZERO по умолчанию (или его содержит @GLOBAL.sql_mode), то корректировать его надо перед каждым набором команд с ALTER TABLE ... AUTO_INCREMENT.

Таблицы постов, пользователей и комментариев

С таблицами wp_posts, wp_users и wp_comments ситуация аналогична — они содержат множество записей с нулевым идентификатором.

В wp_posts такие строки следует удалить: они являются пустыми черновиками, не обладающими контентом.

Восстанавливать пользователей смысла нет: администратор много пользователей зарегистрировать не мог, так как должен был заметить проблемы на экране «Все пользователи» (wp-admin/users.php), а если они регистрировались сами, то уже получили сообщение о невозможности регистрации. В последнем случае, если они захотят зарегистрироваться снова, учётных записей с их почтой быть не должно. Поэтому проблемных пользователей также следует удалить:

Комментарии в wp_comments восстановить возможно, если их добавление не требовало обработки по хукам 'wp_insert_comment', 'comment_post' или 'set_comment_cookies'. Подобная обработка может устанавливать метаданные в wp_commentmeta. Для некорректных комментариев этот хук не активировался, поэтому та информация, что должна была из $_POST поступить в метаданные, была потеряна.

В случае же стандартных комментариев их восстановление заключается в создании уникальных идентификаторов и удалении дубликатов, появившихся при комментировании кем-то из авторизованных администраторов ресурса (см. о проблемах с комментариями).

При удалении дубликатов опираться можно на поля автора, времени создания и контента: у дубликатов они совпадают:

Остальные таблицы

Метаданных для постов, пользователей и комментариев, имеющих нулевой идентификатор, не создавалось. Поэтому в wp_postmeta, wp_usermeta и wp_commentmeta корректировать следует только поле meta_id.

Если таблица не теряла первичный ключ, могут возникнуть коллизии. В этом случае некорректная строка будет только одна, и её можно скорректировать вручную, назначив нулевому идентификатору значение MAX(id) + 1, где id — столбец идентификаторов. В остальных случаях, возможно, окажется полезной заметка о перенумерации при коллизиях.

Термы расположены в двух таблицах — wp_terms и wp_term_taxonomy. Связующим звеном между ними является идентификатор терма term_id, поэтому если термов с нулевым term_id больше двух, восстановить их без ручного вмешательства нельзя. Поэтому их надо удалить. Проблем это не вызовет, так как к постам такие термы не привязываются.

Каждый терм имеет два идентификатора: term_id и term_taxonomy_id (технически на один term_id может приходиться несколько term_taxonomy_id, но на практике такого не происходит). Удаляются те, у которых хотя бы один из них равен нулю:

Ключи восстанавливаются аналогично остальным таблицам:

Далее таблицы надо проверить с помощью выражения SHOW CREATE TABLE, затем отработанные команды следует выполнить в реальной БД и, если всё прошло гладко, восстановить .htaccess в корне сайта.

Замечание: в версии WordPress 4.4 появилась таблица метаданных термов wp_termmeta. Структура её аналогична wp_postmeta, поэтому починка осуществляется также.

Комментарии
Антон
21.04.2023
02:39:28

Здравствуйте, уважаемый! Выражаю огромную благодарность вам за вашу статью! Решило проблему со скоростью работы высоконагруженного сайта на ВП, которому уже 3 года, а проблема была очень давно! И также еще на одном новом сайте.

Индикатор
Андрей
18.03.2018
15:09:49

Здравствуйте, с постами и опциями получилось а вот с рубриками не получается —
SQL запрос:
ALTER TABLE wp_terms CHANGE term_id
    term_id bigint(20) unsigned NOT NULL AUTO_INCREMENT

Ответ MySQL: Документация
#1062 - ALTER TABLE causes auto_increment resequencing, resulting in duplicate entry '1' for key 'PRIMARY'
подскажите если знаете в чем дело? или как это обойти

Индикатор
Готтфрид
19.03.2018
11:46:06

Проблема возникает из-за образования дубликатов первичного ключа при перенумерации таблицы.

Если терм с нулевым term_id только один, то идентификатор можно вручную заменить на MAX(wp_terms.term_id) + 1 в таблицах wp_terms, wp_termmeta и в wp_term_taxonomy.

Если таких термов несколько, то перенумеровать wp_terms не получится, потому что term_id используется для образования связи терма с таксономией в wp_term_taxonomy. Поэтому однозначная связь таких термов с таксономиями уже потеряна. А так как к постам привязываются связки «терм-таксономия» (идентификаторы term_taxonomy_id из wp_term_taxonomy), то и с постами связь также отсутствует.

Поэтому, если строк в wp_terms с нулевым term_id несколько, их имеет смысл удалить.

Тут можно учесть, что термы с нулевыми идентификаторами были добавлены уже после сбоя. В WordPress 4.9.4 они в принципе могут быть привязаны к посту при довольно специфических условиях, но при использовании штатных метабоксов на экране редактирования поста при сохранении возникает проблема. Поэтому потеря данных при удалении термов невелика.

Терм связан с постом таким образом: term_idterm_taxonomy_idpost_id. Первая связь даётся таблицей wp_term_taxonomy, вторая — таблицей wp_term_relationships.
Поэтому надо

  • удалить из wp_term_relationships строки с term_taxonomy_id, которым соответствуют нулевые term_id в таблице wp_term_taxonomy;
  • удалить строки с нулевым term_id из wp_terms, wp_term_taxonomy и wp_termmeta (тут, по идее, метаданных некорректных термов быть не должно, потому что add_metadata(...) не принимает нулевых значений в качестве $object_id).

После этого коллизий возникать не должно.

Аналогичная проблема в других таблицах может быть решена перенумерацией, описанной в заметке про восстановление при коллизиях.

Замечание: перед любыми действиями снимите дамп с БД — чтобы иметь возможность отменить изменения.

Индикатор
Стас
06.07.2017
14:59:59

круто,конечно, но чо куда тыкать непонятно. Может откорректируете статью для «чайников»? больше народа сможет воспользоваться инфой

Индикатор
Павел
12.03.2018
18:50:31

Полностью поддерживаю.
Может это и хороший материал, но для меня он бесполезен оказался. Я так и не нашёл, что надо делать.
Для чайника это слишком заумная инфа а кто пограмотнее, наверняка и сами знают что делать.
Надеялся примерно на такое — открываете то-то-то-то, находите строку такую-то, корректируете то значение на это.
Ни в коем случае не критикую, автор грамотный чел, но для чайников пишется немного примитивнее, чтобы «моя бабушка разобралась не надевая очки».

Индикатор
Готтфрид
13.03.2018
19:44:58

Приведённый инструктаж — это только пример. Шпаргалка, помогающая вспомнить суть проблемы и принцип её решения.

В большинстве случаев всплывают неожиданные детали, которые нельзя игнорировать и для разрешения которых нужна импровизация. Поэтому знание MySQL и понимание устройства БД WordPress абсолютно необходимы.

Поэтому упростить материал нельзя — потеряется смысл.

Пытаться восстанавливать БД сразу на реальном сайте без полного понимания происходящего тоже нельзя. Потому что ошибка здесь способна привести к потере данных, без которых полноценная работа WP, темы и/или каких-либо плагинов окажется невозможной.

Поэтому имеет смысл обратиться к специалистам, занимающимся восстановлением WP — таких много на любой бирже фриланса.

В этом случае надо заранее иметь в виду, что специалисту может потребоваться полный доступ к ресурсу: к БД, к файлам по ftp и в админку. Поэтому тут встаёт вопрос доверия. Также не забудьте заранее сделать дамп БД и скопировать с ресурса все файлы — на случай, если выбранный специалист изменит БД, но не добьётся результата, откажется от задачи и изменения придётся откатывать.

Индикатор
Валихан
05.08.2016
16:17:43

Спасибо. С помощью этой статьи сайт заработал, но появилась проблема загрузки изображения при загрузки, картинка физически грузится но не появляется иконка в медиаменеджере(как будто отсутствует путь до неё). по этой причине и миниатюры не загрузить.
Второй глюк это проблема с созданием нового пункта меню, появляется пункт но без ссылки на материал. Можете что-нибудь посоветовать?

Индикатор
Готтфрид
06.08.2016
01:47:02

Скорее всего отсутствует автоинкремент у поля meta_id таблицы wp_postmeta.
В этом случае в wp-4.5.3 вижу такое:

  • при загрузке вложения на экране «Медиафайлы > Библиотека» вместо загруженной картинки отображается иконка в виде пустой страницы;
  • при добавлении ссылки на страницу в меню пункт выкрашен в розовый цвет с надписью «(Некорректно)», а если добавлять рубрику — «(На утверждении)»; ссылка на страницу/архив/etc. отсутствует.
Индикатор
Валихан
08.08.2016
11:13:04

Спасибо, Вы оказались правы. )
Красава.)

Индикатор
Дмитрий
05.04.2018
23:25:56

Так а что делать чтобы решить эту проблему?

Индикатор
Готтфрид
06.04.2018
00:35:54

Надо добавить автоинкремент, как описано выше в разделе «Остальные таблицы».
Если возникнет проблема с повторяющимися ключами, то её решение описано в заметке про коллизии.

Индикатор
Кирилл
09.06.2016
19:18:35

Да мой друг, у тебя действительно полезный материал по данной проблеме.
Спасибо!

Индикатор
Василий
13.12.2015
06:07:25

Вы мой спаситель, 2 дня не мог разобраться в данной проблеме, это единственное правильное решение. Большое вам спасибо.

Индикатор
Алексей [mozg]
27.08.2015
23:23:55

огромная благодарность за этот пост. Долго пришлось вникать, но это единственный в рунете материал по этой проблеме с базой в ВП.
Удалось починить все кроме комментариев, но есть подозрение что с ними проблема в шаблоне, и не связана с инкрементом и первичным ключом в таблице.

Спасибо!

Индикатор
Оставить комментарий

Добавить комментарий