Как безопасно удалить дубли в MySQL и не потерять связанные данные

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

Главная ошибка — найти дубли через GROUP BY и сразу удалить все лишние строки. Так легко потерять связанную информацию или оставить внешние ключи на удалённую запись.

Сначала найти дубли

Пример: есть товары с одинаковым артикулом у одного поставщика.

SELECT supplier_id, sku, COUNT(*) AS cnt
FROM supplier_products
WHERE sku IS NOT NULL AND sku != ''
GROUP BY supplier_id, sku
HAVING cnt > 1;

Такой запрос показывает группы дублей, но ещё не говорит, какую запись удалять. Нужно выбрать основную.

Посмотреть конкретную группу

Перед массовой чисткой нужно открыть несколько примеров руками.

SELECT *
FROM supplier_products
WHERE supplier_id = 10
  AND sku = 'ABC-123'
ORDER BY id;

Важно понять, чем записи отличаются: датой создания, внешним ID, остатками, ценой, активностью, связями с системным товаром.

Выбрать основную запись

Правило выбора должно быть явным. Например:

  • оставить запись с непустым external_id;
  • оставить самую новую запись;
  • оставить запись, связанную с заказами;
  • оставить активную запись;
  • оставить запись с максимальным id только если это действительно правильно.

Правило “оставить MIN(id)” удобно технически, но не всегда верно бизнес-логически.

Проверить связи

Перед удалением нужно найти таблицы, которые ссылаются на дубли.

SELECT COUNT(*)
FROM order_items
WHERE supplier_product_id IN (101, 102, 103);

Если связи есть, их нужно перенести на основную запись или отказаться от удаления.

UPDATE order_items
SET supplier_product_id = 101
WHERE supplier_product_id IN (102, 103);

Такое обновление нужно делать только после проверки, что 101 действительно основная запись.

Временная таблица для плана удаления

Хороший подход — сначала сформировать таблицу соответствий: какой дубль заменить на какую основную запись.

CREATE TEMPORARY TABLE duplicate_product_map (
    duplicate_id INT NOT NULL,
    main_id INT NOT NULL,
    PRIMARY KEY (duplicate_id)
);

После заполнения такой таблицы можно проверить план до удаления.

SELECT *
FROM duplicate_product_map
LIMIT 50;

Транзакция

Если объём небольшой, перенос связей и удаление дублей лучше делать в транзакции.

START TRANSACTION;

UPDATE order_items oi
JOIN duplicate_product_map m ON m.duplicate_id = oi.supplier_product_id
SET oi.supplier_product_id = m.main_id;

DELETE sp
FROM supplier_products sp
JOIN duplicate_product_map m ON m.duplicate_id = sp.id;

COMMIT;

На больших таблицах транзакция может быть тяжёлой. Тогда лучше делать пакетами и иметь проверенный бэкап.

Добавить уникальный индекс после чистки

Если после удаления дублей не добавить ограничение, дубли появятся снова.

CREATE UNIQUE INDEX ux_supplier_products_supplier_sku
ON supplier_products (supplier_id, sku);

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

Не забыть логи и бэкап

Перед чисткой нужен бэкап. После чистки — отчёт: сколько групп дублей найдено, сколько связей перенесено, сколько строк удалено, какие ошибки были.

Чек-лист

  1. Найти группы дублей через GROUP BY.
  2. Разобрать несколько групп вручную.
  3. Определить правило выбора основной записи.
  4. Проверить все связанные таблицы.
  5. Сформировать таблицу соответствий duplicate_id → main_id.
  6. Перенести связи на основную запись.
  7. Удалить только подтверждённые дубли.
  8. Добавить уникальный индекс или другое ограничение.
  9. Сохранить отчёт и проверить результат.

Удаление дублей — это не одна SQL-команда, а маленькая миграция данных. Чем важнее таблица, тем осторожнее нужен план: бэкап, связи, основная запись, транзакция и защита от повторного появления дублей.

Комментарии (0)

Пока нет комментариев. Будьте первым!