- 19.07.15
- 12:14
- 11916
- 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
, поэтому починка осуществляется также.
bobkdb
7j1y5q
lvrsx7
5htbba
tkeqa8
v89cdx
uc5fr0
s6bvjt
50kfya
e7s8lk
ic3j7u
9zcp19
tplck5
emuglx
qcwe1g
gl0x2c
3tx2bv
Здравствуйте, уважаемый! Выражаю огромную благодарность вам за вашу статью! Решило проблему со скоростью работы высоконагруженного сайта на ВП, которому уже 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 дня не мог разобраться в данной проблеме, это единственное правильное решение. Большое вам спасибо.
огромная благодарность за этот пост. Долго пришлось вникать, но это единственный в рунете материал по этой проблеме с базой в ВП.
Удалось починить все кроме комментариев, но есть подозрение что с ними проблема в шаблоне, и не связана с инкрементом и первичным ключом в таблице.
Спасибо!