- 19.07.15
- 12:14
- 10370
- 14
Дан ресурс на WordPress, у таблиц которого отсутствует автоинкремент и первичный ключ. Как так получилось — неизвестно, предполагать не буду. Ресурс проработал в таком состоянии почти месяц, в течение которого пользователи не могли регистрироваться, невозможно было создавать новые посты, наблюдались проблемы с комментированием.
Симптомы описаны здесь.
На время починки ресурса его следует запереть при помощи .htaccess
, размещённом в корне сайта. Для apache 2.4 в .htaccess
следует добавить строку
1 |
Require all denied |
и закомментировать строку "Require all granted"
, если таковая имеется.
Для apache 2.2 и более старых версий .htaccess
должен содержать
1 2 |
Order Deny,Allow Deny from all |
Устанавливать и/или активировать плагины и изменять их настройки при таком положении дел не безопасно: вся работа с ними опирается на базу данных. Её использование в этом состоянии может усугубить ситуацию, создав множество некорректных записей, вычленить каждую из которых до возникновения из-за неё какой-либо проблемы будет невозможно.
Хотя в текущей версии WordPress (4.2.2) изменение опций в принципе допустимо — потому что работа с ними ведётся по именам, а не по идентификаторам — стабильность гарантировать нельзя.
Все изменения в базу данных следует вносить только после отработки их на некоторой экспериментальной БД. Каждая отработанная команда добавляется в SQL-скрипт, который затем, когда все экспериментальные таблицы будут налажены, просто запускается в БД боевого сервера. Это существенно снижает риск окончательной поломки ресурса.
Перед началом все восстанавливаемые таблицы следует задампить — как для копирования в экспериментальную базу и, возможно, её неоднократного восстановления, так и на случай неудачной починки, проблемы из-за которой могут проявиться не сразу. В самом простом случае дампу подлежат следующие 12 таблиц:
|
|
|
В качестве префикса таблиц взят стандартный — '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'
:
1 2 3 4 5 6 7 8 9 10 11 12 |
mysql> SHOW CREATE TABLE wp_options\G *************************** 1. row *************************** Table: wp_options Create Table: CREATE TABLE `wp_options` ( `option_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `option_name` varchar(64) NOT NULL DEFAULT '', `option_value` longtext NOT NULL, `autoload` varchar(20) NOT NULL DEFAULT 'yes', PRIMARY KEY (`option_id`), UNIQUE KEY `option_name` (`option_name`) ) ENGINE=MyISAM AUTO_INCREMENT=3789 DEFAULT CHARSET=utf8 1 row in set (0.00 sec) |
Поле идентификаторов во всех корректных таблицах WordPress задано одинаково:
- это первое поле таблицы;
- оно определено как
bigint(20) unsigned NOT NULL AUTO_INCREMENT
; - оно является первичным ключом.
Исключение составляет только wp_term_relationships
— идентификатором её записи не обладают, а в качестве первичного ключа выступает пара (object_id, term_taxonomy_id)
.
У мультисайтов есть ещё одно исключение — wp_blog_versions
, идентификаторы блогов в которой являются ключом, но не имеют автоинкремента, т.к. формируются при создании записей в wp_blogs
.
Для определения корректности формата таблицы также можно использовать DESCRIBE
:
1 2 3 4 5 6 7 8 9 |
mysql> DESCRIBE wp_options; +--------------+---------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +--------------+---------------------+------+-----+---------+----------------+ | option_id | bigint(20) unsigned | NO | PRI | NULL | auto_increment | | option_name | varchar(64) | NO | UNI | | | | option_value | longtext | NO | | NULL | | | autoload | varchar(20) | NO | | yes | | +--------------+---------------------+------+-----+---------+----------------+ |
Для примера, нездоровая таблица wp_options
выглядела так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
mysql> DESCRIBE wp_options; +--------------+---------------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +--------------+---------------------+------+-----+---------+-------+ | option_id | bigint(20) unsigned | NO | | NULL | | | option_name | varchar(64) | NO | PRI | | | | option_value | longtext | NO | | NULL | | | autoload | varchar(20) | NO | | yes | | +--------------+---------------------+------+-----+---------+-------+ 4 rows in set (0.00 sec) mysql> SHOW CREATE TABLE wp_options\G *************************** 1. row *************************** Table: wp_options Create Table: CREATE TABLE `wp_options` ( `option_id` bigint(20) unsigned NOT NULL, `option_name` varchar(64) NOT NULL DEFAULT '', `option_value` longtext NOT NULL, `autoload` varchar(20) NOT NULL DEFAULT 'yes', UNIQUE KEY `option_name` (`option_name`) ) ENGINE=MyISAM AUTO_INCREMENT=32243 DEFAULT CHARSET=utf8 1 row in set (0.00 sec) |
После изучения таблиц с помощью 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 |
Таблица опций
Значение полей с автоинкрементом всегда должны быть положительны. Отсутствие автоинкремента приводит ко множеству нулевых идентификаторов:
1 2 3 4 5 6 7 8 |
mysql> SELECT COUNT(option_id) AS x, option_id FROM wp_options -> GROUP BY option_id HAVING x > 1; +----+-----------+ | x | option_id | +----+-----------+ | 54 | 0 | +----+-----------+ 1 row in set (0.00 sec) |
На данный момент WordPress при работе с опциями опирается на option_name
, а от идентификаторов требуется только уникальность, поэтому все нули в option_id
можно заполнить автоинкрементом. Чтобы нули заменялись автоматически, следует убедиться, что параметр @@sql_mode
не содержит NO_AUTO_VALUE_ON_ZERO
:
1 2 3 |
SET @@SESSION.sql_mode = REPLACE(@@sql_mode, 'NO_AUTO_VALUE_ON_ZERO', ''); ALTER TABLE wp_options CHANGE option_id option_id bigint(20) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY; |
Если возникают коллизии, то перенумерацию можно провести обходным путём.
Замечание: при работе с phpMyAdmin следует учитывать, что каждый набор запросов выполняется в собственной сессии. Поэтому если @@sql_mode
содержит NO_AUTO_VALUE_ON_ZERO
по умолчанию (или его содержит @GLOBAL.sql_mode
), то корректировать его надо перед каждым набором команд с ALTER TABLE ... AUTO_INCREMENT
.
Таблицы постов, пользователей и комментариев
С таблицами wp_posts
, wp_users
и wp_comments
ситуация аналогична — они содержат множество записей с нулевым идентификатором.
В wp_posts
такие строки следует удалить: они являются пустыми черновиками, не обладающими контентом.
1 2 |
DELETE FROM wp_posts WHERE ID = 0; ALTER TABLE wp_posts CHANGE ID ID bigint(20) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY; |
Восстанавливать пользователей смысла нет: администратор много пользователей зарегистрировать не мог, так как должен был заметить проблемы на экране «Все пользователи» (wp-admin/users.php
), а если они регистрировались сами, то уже получили сообщение о невозможности регистрации. В последнем случае, если они захотят зарегистрироваться снова, учётных записей с их почтой быть не должно. Поэтому проблемных пользователей также следует удалить:
1 2 |
DELETE FROM wp_users WHERE ID = 0; ALTER TABLE wp_users CHANGE ID ID bigint(20) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY; |
Комментарии в wp_comments
восстановить возможно, если их добавление не требовало обработки по хукам 'wp_insert_comment'
, 'comment_post'
или 'set_comment_cookies'
. Подобная обработка может устанавливать метаданные в wp_commentmeta
. Для некорректных комментариев этот хук не активировался, поэтому та информация, что должна была из $_POST
поступить в метаданные, была потеряна.
В случае же стандартных комментариев их восстановление заключается в создании уникальных идентификаторов и удалении дубликатов, появившихся при комментировании кем-то из авторизованных администраторов ресурса (см. о проблемах с комментариями).
1 2 |
ALTER TABLE wp_comments CHANGE comment_ID `comment_ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY; |
При удалении дубликатов опираться можно на поля автора, времени создания и контента: у дубликатов они совпадают:
1 2 3 4 5 |
DELETE x FROM wp_comments AS x INNER JOIN wp_comments AS y ON (x.comment_date_gmt = y.comment_date_gmt AND x.comment_author = y.comment_author AND x.comment_content = y.comment_content AND x.comment_ID < y.comment_ID); |
Остальные таблицы
Метаданных для постов, пользователей и комментариев, имеющих нулевой идентификатор, не создавалось. Поэтому в wp_postmeta
, wp_usermeta
и wp_commentmeta
корректировать следует только поле meta_id
.
1 2 3 4 5 6 7 8 |
ALTER TABLE wp_usermeta CHANGE umeta_id umeta_id bigint(20) unsigned NOT NULL AUTO_INCREMENT; ALTER TABLE wp_postmeta CHANGE meta_id meta_id bigint(20) unsigned NOT NULL AUTO_INCREMENT; ALTER TABLE wp_commentmeta CHANGE meta_id meta_id bigint(20) unsigned NOT NULL AUTO_INCREMENT; ALTER TABLE wp_links CHANGE link_id link_id bigint(20) unsigned NOT NULL AUTO_INCREMENT; |
Если таблица не теряла первичный ключ, могут возникнуть коллизии. В этом случае некорректная строка будет только одна, и её можно скорректировать вручную, назначив нулевому идентификатору значение MAX(id) + 1
, где id
— столбец идентификаторов. В остальных случаях, возможно, окажется полезной заметка о перенумерации при коллизиях.
Термы расположены в двух таблицах — wp_terms
и wp_term_taxonomy
. Связующим звеном между ними является идентификатор терма term_id
, поэтому если термов с нулевым term_id
больше двух, восстановить их без ручного вмешательства нельзя. Поэтому их надо удалить. Проблем это не вызовет, так как к постам такие термы не привязываются.
Каждый терм имеет два идентификатора: term_id
и term_taxonomy_id
(технически на один term_id
может приходиться несколько term_taxonomy_id
, но на практике такого не происходит). Удаляются те, у которых хотя бы один из них равен нулю:
1 2 3 4 5 6 |
DELETE x, y FROM wp_terms AS x INNER JOIN wp_term_taxonomy AS y ON (x.term_id = y.term_id) WHERE x.term_id = 0 OR y.term_taxonomy_id = 0; -- на случай строк, у которых соответствие по term_id отсутствует: DELETE FROM wp_terms WHERE term_id = 0; DELETE FROM wp_term_taxonomy WHERE term_taxonomy_id = 0; |
Ключи восстанавливаются аналогично остальным таблицам:
1 2 3 4 5 6 7 |
-- добавить ключи, если отсутствуют: -- ALTER TABLE wp_terms ADD PRIMARY KEY (term_id); -- ALTER TABLE wp_term_taxonomy ADD PRIMARY KEY (term_taxonomy_id); ALTER TABLE wp_terms CHANGE term_id term_id bigint(20) unsigned NOT NULL AUTO_INCREMENT; ALTER TABLE wp_term_taxonomy CHANGE term_taxonomy_id term_taxonomy_id bigint(20) unsigned NOT NULL AUTO_INCREMENT; |
Далее таблицы надо проверить с помощью выражения SHOW CREATE TABLE
, затем отработанные команды следует выполнить в реальной БД и, если всё прошло гладко, восстановить .htaccess
в корне сайта.
Замечание: в версии WordPress 4.4 появилась таблица метаданных термов wp_termmeta
. Структура её аналогична wp_postmeta
, поэтому починка осуществляется также.
Здравствуйте, уважаемый! Выражаю огромную благодарность вам за вашу статью! Решило проблему со скоростью работы высоконагруженного сайта на ВП, которому уже 3 года, а проблема была очень давно! И также еще на одном новом сайте.
Здравствуйте, с постами и опциями получилось а вот с рубриками не получается —
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'
подскажите если знаете в чем дело? или как это обойти
Проблема возникает из-за образования дубликатов первичного ключа при перенумерации таблицы.
Если терм с нулевым
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_id
—term_taxonomy_id
—post_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
).После этого коллизий возникать не должно.
Аналогичная проблема в других таблицах может быть решена перенумерацией, описанной в заметке про восстановление при коллизиях.
Замечание: перед любыми действиями снимите дамп с БД — чтобы иметь возможность отменить изменения.
круто,конечно, но чо куда тыкать непонятно. Может откорректируете статью для «чайников»? больше народа сможет воспользоваться инфой
Полностью поддерживаю.
Может это и хороший материал, но для меня он бесполезен оказался. Я так и не нашёл, что надо делать.
Для чайника это слишком заумная инфа а кто пограмотнее, наверняка и сами знают что делать.
Надеялся примерно на такое — открываете то-то-то-то, находите строку такую-то, корректируете то значение на это.
Ни в коем случае не критикую, автор грамотный чел, но для чайников пишется немного примитивнее, чтобы «моя бабушка разобралась не надевая очки».
Приведённый инструктаж — это только пример. Шпаргалка, помогающая вспомнить суть проблемы и принцип её решения.
В большинстве случаев всплывают неожиданные детали, которые нельзя игнорировать и для разрешения которых нужна импровизация. Поэтому знание MySQL и понимание устройства БД WordPress абсолютно необходимы.
Поэтому упростить материал нельзя — потеряется смысл.
Пытаться восстанавливать БД сразу на реальном сайте без полного понимания происходящего тоже нельзя. Потому что ошибка здесь способна привести к потере данных, без которых полноценная работа WP, темы и/или каких-либо плагинов окажется невозможной.
Поэтому имеет смысл обратиться к специалистам, занимающимся восстановлением WP — таких много на любой бирже фриланса.
В этом случае надо заранее иметь в виду, что специалисту может потребоваться полный доступ к ресурсу: к БД, к файлам по ftp и в админку. Поэтому тут встаёт вопрос доверия. Также не забудьте заранее сделать дамп БД и скопировать с ресурса все файлы — на случай, если выбранный специалист изменит БД, но не добьётся результата, откажется от задачи и изменения придётся откатывать.
Спасибо. С помощью этой статьи сайт заработал, но появилась проблема загрузки изображения при загрузки, картинка физически грузится но не появляется иконка в медиаменеджере(как будто отсутствует путь до неё). по этой причине и миниатюры не загрузить.
Второй глюк это проблема с созданием нового пункта меню, появляется пункт но без ссылки на материал. Можете что-нибудь посоветовать?
Скорее всего отсутствует автоинкремент у поля
meta_id
таблицыwp_postmeta
.В этом случае в wp-4.5.3 вижу такое:
Спасибо, Вы оказались правы. )
Красава.)
Так а что делать чтобы решить эту проблему?
Надо добавить автоинкремент, как описано выше в разделе «Остальные таблицы».
Если возникнет проблема с повторяющимися ключами, то её решение описано в заметке про коллизии.
Да мой друг, у тебя действительно полезный материал по данной проблеме.
Спасибо!
Вы мой спаситель, 2 дня не мог разобраться в данной проблеме, это единственное правильное решение. Большое вам спасибо.
огромная благодарность за этот пост. Долго пришлось вникать, но это единственный в рунете материал по этой проблеме с базой в ВП.
Удалось починить все кроме комментариев, но есть подозрение что с ними проблема в шаблоне, и не связана с инкрементом и первичным ключом в таблице.
Спасибо!