Дубли в WordPress не всегда выглядят как одинаковые заголовки в списке записей. Часто проблема прячется глубже: одна и та же запись получает несколько наборов метаданных, а потом это превращается в повторные карточки, лишние результаты поиска, дубли в выдаче и путаницу в админке. Особенно это заметно после импорта, миграции, сбоя плагина или неаккуратной синхронизации через код.
В этой статье разберём именно такой сценарий: как найти дубликаты записей, которые связаны с postmeta, и удалить их без лишнего риска. Речь не про «удалить всё подряд», а про проверяемую процедуру: сначала диагностика, потом выбор правила, потом очистка и проверка результата.
Когда проблема действительно в дубликатах postmeta
Симптомы обычно похожи, но причина может быть разной. Если у записи одинаковый контент, но разные ID, это один случай. Если у одной записи повторяются метаполя, а в интерфейсе появляются лишние блоки, дубли в списке или странное поведение фильтров, это уже другой сценарий. Важно не путать их, потому что метод очистки будет отличаться.
Типичные признаки
- после импорта появились записи с одинаковым заголовком и одинаковым
post_content; - в админке один и тот же объект отображается несколько раз в разных фильтрах;
- плагин выгружает повторяющиеся карточки из-за одинаковых мета-значений;
- поиск по сайту показывает несколько почти одинаковых страниц;
- в базе растёт число строк в
wp_postmeta, хотя контента не прибавляется.
Что проверить до удаления
Сначала нужно понять, что именно считать дублем. Для одного проекта это может быть одинаковый post_title и post_content, для другого — совпадение по ключевому метаполю, например external_id или source_id. Без этого легко удалить не ту запись.
- сделайте резервную копию базы данных;
- проверьте, есть ли у дублей общий мета-ключ, по которому их можно группировать;
- сравните ID, дату создания и автора;
- посмотрите, не используются ли эти записи в меню, виджетах, блоках или внутренних ссылках;
- убедитесь, что дубли не создаются автоматически при сохранении записи.
Как найти дубликаты через SQL-запрос
Если у записей есть стабильный мета-ключ, по которому можно определить повтор, проще всего начать с SQL. Это быстрее и точнее, чем вручную листать админку. Ниже пример для случая, когда дубли группируются по значению external_id в postmeta.
SELECT pm.meta_value AS external_id, COUNT(*) AS cnt
FROM wp_postmeta pm
JOIN wp_posts p ON p.ID = pm.post_id
WHERE pm.meta_key = 'external_id'
AND p.post_type = 'post'
AND p.post_status NOT IN ('trash', 'auto-draft')
GROUP BY pm.meta_value
HAVING COUNT(*) > 1
ORDER BY cnt DESC;Этот запрос покажет группы, где один и тот же внешний идентификатор встречается несколько раз. Дальше можно посмотреть конкретные записи:
SELECT p.ID, p.post_title, p.post_date, pm.meta_value AS external_id
FROM wp_posts p
JOIN wp_postmeta pm ON pm.post_id = p.ID
WHERE pm.meta_key = 'external_id'
AND pm.meta_value = '12345'
ORDER BY p.post_date ASC;Если у вас нет стабильного мета-ключа, можно искать дубли по сочетанию заголовка и контента, но это уже менее надёжно. В таком случае лучше сначала определить правило вручную на небольшой выборке, а потом автоматизировать удаление.
Пошаговое удаление дублей через код
Для безопасной очистки удобнее написать небольшой скрипт, который сначала находит кандидатов, а потом удаляет только лишние записи. Ниже пример для functions.php дочерней темы или, лучше, для мини-плагина. Скрипт оставляет самую старую запись для каждого external_id, а остальные удаляет в корзину.
add_action('admin_init', function () {
if (!current_user_can('manage_options')) {
return;
}
if (!isset($_GET['cleanup_duplicates']) || $_GET['cleanup_duplicates'] !== '1') {
return;
}
global $wpdb;
$meta_key = 'external_id';
$post_type = 'post';
$rows = $wpdb->get_results(
$wpdb->prepare(
"SELECT pm.meta_value, GROUP_CONCAT(p.ID ORDER BY p.post_date ASC) AS ids, COUNT(*) AS cnt
FROM {$wpdb->postmeta} pm
INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
WHERE pm.meta_key = %s
AND p.post_type = %s
AND p.post_status NOT IN ('trash', 'auto-draft')
GROUP BY pm.meta_value
HAVING cnt > 1",
$meta_key,
$post_type
)
);
foreach ($rows as $row) {
$ids = array_map('intval', explode(',', $row->ids));
array_shift($ids); // оставляем самую старую запись
foreach ($ids as $post_id) {
wp_trash_post($post_id);
}
}
wp_die('Дубликаты обработаны. Проверьте корзину и результаты выборки.');
});Запускать такой код нужно только временно. После проверки удалите его, чтобы не оставить в админке опасный триггер по URL-параметру.
Если нужно удалить записи окончательно
Когда вы уверены в результате и есть бэкап, можно заменить wp_trash_post() на wp_delete_post( $post_id, true ). Но это уже необратимое удаление, поэтому сначала проверьте корзину и убедитесь, что записи нигде не используются.
Сравнение подходов: плагин, SQL или код
| Подход | Когда подходит | Плюсы | Минусы |
|---|---|---|---|
| Плагин очистки | Если нужно быстро убрать мусор без разработки | Меньше ручной работы, проще для редактора | Не всегда учитывает вашу логику дублей |
| SQL-запрос | Когда есть чёткий мета-ключ и понятное правило | Быстро, прозрачно, легко проверить выборку | Ошибочный запрос может затронуть лишние записи |
Код через wp_trash_post() | Когда нужна повторяемая очистка | Можно встроить в админский процесс и логировать | Нужна аккуратность и тест на копии базы |
Если задача разовая, обычно достаточно SQL-диагностики и ручного удаления через админку. Если дубли появляются регулярно, лучше искать источник в импорте, cron-задаче или плагине синхронизации.
Как проверить, что очистка сработала
После удаления не ограничивайтесь визуальной проверкой в списке записей. Нужно убедиться, что дубли исчезли и не появились новые.
- повторно выполните SQL-запрос с
GROUP BYи проверьте, что групп сCOUNT(*) > 1больше нет; - откройте несколько удалённых URL и проверьте, что они ведут в корзину, 404 или на корректный редирект, если он настроен;
- проверьте внутренний поиск сайта и архивы таксономий;
- если используется кэш, очистите его и сравните выдачу до и после;
- посмотрите, не остались ли ссылки на удалённые записи в меню, блоках и виджетах.
Для сайтов с индексацией важно отдельно проверить sitemap и robots-логику. Если дубли уже попали в индекс, одного удаления из базы недостаточно — поисковику нужно дать сигнал через 404, 410 или редирект, в зависимости от сценария.
Частые ошибки и как их исправить
Удаляют по заголовку, а не по стабильному идентификатору
Одинаковый заголовок ещё не означает дубль. У разных авторов могут быть одинаковые названия, а контент — разный. Без мета-ключа или другого уникального признака легко снести рабочую запись.
Не проверяют вложенные связи
Запись может быть родителем для страниц, прикреплённых файлов или элементов конструктора. Перед удалением проверьте, не используется ли её ID в других таблицах и полях темы или плагина.
Чистят только wp_posts, забывая про wp_postmeta
Если удалить запись вручную, но оставить мусорные метаданные, база всё равно будет разрастаться. В норме WordPress удаляет связанные мета-данные вместе с постом, но при нестандартных скриптах и прямом SQL это не всегда происходит автоматически.
Запускают очистку на боевом сайте без бэкапа
Это самая дорогая ошибка. Даже если логика кажется простой, сначала проверьте её на копии базы или staging-окружении. Особенно если сайт давно живёт, а данные в него попадают из нескольких источников.
Что делать, если дубли создаются снова
Если очистка помогла, но проблема возвращается, искать нужно не в базе, а в источнике данных. Обычно это один из трёх вариантов: импорт, синхронизация по API или криво написанный обработчик сохранения записи.
- проверьте, не создаёт ли плагин импорта новую запись при каждом запуске вместо обновления существующей;
- посмотрите, не срабатывает ли
save_postбез защиты от повторного вызова; - проверьте, не дублирует ли внешний сервис один и тот же объект с новым ID;
- если дубли идут из формы или REST-запроса, убедитесь, что есть проверка уникальности по
external_idили другому ключу.
Для профилактики полезно хранить в метаданных внешний идентификатор источника и перед созданием новой записи сначала искать уже существующую по этому значению. Это проще, чем потом чистить сотни повторов.
Практические советы по безопасности и производительности
Очистка дублей — это не только про порядок в админке. Лишние записи и мета-строки увеличивают объём запросов, мешают фильтрам и могут замедлять выборки в списках. Если база уже разрослась, после удаления имеет смысл проверить индексы и общий объём таблиц, но делать это стоит только понимая нагрузку и структуру сайта.
Для регулярной работы лучше вынести логику в отдельный мини-плагин или использовать готовый инструмент для чистки дублей и SEO-мусора, например Clearfy Pro: https://wpshop.ru/plugins/clearfy. Но даже в этом случае правило дубля нужно проверять под ваш проект, а не включать очистку вслепую.
Если вы работаете с боевым сайтом, придерживайтесь простой схемы: сначала копия базы, потом выборка, потом удаление в корзину, и только после этого окончательная очистка. Такой порядок экономит время лучше, чем попытка чинить последствия после массового удаления.