Установка плагина завершается ошибкой:

 Duplicate entry '0' for key 'PRIMARY'
 

Очевидное прочтение заключается в том, что плагин попытался вставить строку с id 0. Реальное положение дел обычно противоположно: плагин вставил строку  без указания имени колонки id, полагаясь на то, что  AUTO_INCREMENT  предоставит его, и получил  0 , поскольку у колонки больше нет атрибута  AUTO_INCREMENT . Вторая такая вставка затем конфликтует с первой.

Это не баг плагина, и попытки исправить его как баг лишь тратят кучу времени. Вот как это выглядит, почему распространяется и как это исправить, не наступая на те же грабли дважды.

Признак: проблема перемещается

Впервые мы столкнулись с этим при установке модуля в магазине на PrestaShop. Ошибка последовательно проходила по таблицам:

  1.  ps_configuration  — первая настройка, которую записывает модуль
  2. затем  ps_log  — строка «начало установки модуля»
  3. затем  ps_module  — строка, регистрирующая сам модуль

Исправьте одну таблицу, запустите установку снова — ошибка возникнет на следующей. Эта игра в «молотобойца» и есть диагностический признак.  Баг плагина не мигрирует в таблицы ядра. Если ваша ошибка меняет таблицу каждый раз, когда вы исправляете предыдущую, повреждение затрагивает всю базу данных.

Позже в том же магазине произошел сбой на витрине: добавление любого товара в корзину вызывало ту же ошибку в  INSERT INTO ps_cart . Та же причина, другая таблица, к которой еще никто не прикасался. Если бы дело дошло до оформления заказа, следующим стал бы  ps_orders .

Откуда это берется

Некорректный импорт или восстановление.  mysqldump , дампы phpMyAdmin и различные инструменты миграции могут создавать схему, которая воссоздает первичный ключ  без атрибута  AUTO_INCREMENT  — и такое восстановление часто оставляет внутренние счетчики устаревшими, из-за чего строки застревают со значением id 0.

В восстановленном нами магазине каждый  AUTO_INCREMENT  в базе данных исчез: товары, комбинации, модули, конфигурация, сотрудники, магазины, языки и около пятидесяти пяти таблиц модулей.

Ловушка при исправление

Естественное исправление выглядит так:

 ALTER TABLE `ps_log` MODIFY `id_log` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT;
 

что приводит к ошибке:

 #1062 - ALTER TABLE causes auto_increment resequencing,
        resulting in duplicate entry 'N' for key 'PRIMARY'
 

Добавление  AUTO_INCREMENT  заставляет MySQL заново пронумеровать существующую строку  id = 0 , а устаревший счетчик присваивает ей id, который уже используется.

Предотвратите перенумерацию:

 SET SESSION sql_mode = 'NO_AUTO_VALUE_ON_ZERO';
ALTER TABLE `ps_log`           MODIFY `id_log`           INT(10) UNSIGNED NOT NULL AUTO_INCREMENT;
ALTER TABLE `ps_configuration` MODIFY `id_configuration` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT;
ALTER TABLE `ps_module`        MODIFY `id_module`        INT(10) UNSIGNED NOT NULL AUTO_INCREMENT;
 

 NO_AUTO_VALUE_ON_ZERO  указывает MySQL воспринимать литерал  0  как реальное значение, а не как запрос на получение следующего id, поэтому существующая строка сохраняет свой id, и ничего не перенумеровывается.

 Запускайте  SET  и  ALTER  в рамках одного запроса (сессии отправки).  SET SESSION  действует в рамках только одного соединения; phpMyAdmin может выделить вам другое соединение для отдельного запроса, и тогда  ALTER  завершится ошибкой точно так же, как и раньше, в то время как вы уверены, что только что установили режим.

Поиск всех затронутых таблиц

Не делайте это вручную. Спросите схему:

 SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'your_database_name'
  AND COLUMN_KEY = 'PRI'
  AND EXTRA NOT LIKE '%auto_increment%'
  AND DATA_TYPE IN ('int','bigint','smallint','mediumint','tinyint')
ORDER BY TABLE_NAME;
 

 Жестко прописывайте имя схемы. Генератор, построенный на  DATABASE() , ничего не возвращает, когда phpMyAdmin находится в контексте  information_schema , что часто случается, если вы перешли через обозреватель схем. Запрос выполняется, возвращает ноль строк, и вы приходите к выводу, что с базой данных все в порядке.

Этот запрос выводит список кандидатов. Он не выводит готовые исправления — и эта разница имеет значение, поскольку некоторые из этих первичных ключей законно не являются автоинкрементируемыми.

Что следует исключить

Слепое применение ALTER ко всему, что возвращает запрос, повременит вашу базу данных. Исключите:

  •  Все составные первичные ключи.  AUTO_INCREMENT  применяется к единственной колонке, которая является самой левой частью ключа. Многоколоночные PK в этом списке являются связями и таблицами сопряжения, их трогать нельзя.
  •  Одноколоночные целочисленные PK, которые по задумке не являются автоинкрементируемыми — внешний ключ, дублирующий первичный ключ. В PrestaShop к ним относятся  ps_address_format  ( id_country ),  ps_product_sale  ( id_product ),  ps_ip2location  ( ip_to ), таблицы  ps_layered_indexable_* ,  ps_pscheckout_address  /  ps_pscheckout_customer  ( id_customer ),  ps_psshipping_address  и таблицы  ps_eventbus_* . В вашей схеме будут свои эквиваленты: критерий заключается в том, назначается ли значение другой таблицей или генерируется здесь.

Два особых случая, о которых стоит знать, поскольку они выглядят как исключения, но ими не являются:  ps_customization.id_customization  и  ps_cms_role.id_cms_role  входят в составные первичные ключи, но правомерно являются  AUTO_INCREMENT  в качестве самой левой колонки.

Рабочий процесс: сгенерируйте инструкции  ALTER  с помощью запроса, который объединяет их через CONCAT,  прочитайте вывод, удалите строки, принадлежащие списку исключений, а затем запустите то, что осталось. Два типа ошибок, с которыми мы столкнулись, делая ровно это:

  • Запуск генератора с верой в то, что это что-то исправило. Он лишь выводит инструкции. Вам нужно запустить полученный результат.
  • Удаление строк  id = 0  вместо запуска ALTER. Это устраняет непосредственный конфликт, но оставляет схему сломанной, поэтому проблема возвращается при следующей вставке.

Еще одна операционная деталь: если вы вставляете большой пакет в phpMyAdmin и переносы строк теряются, комментарий  --  «проглотит» следующую за ним инструкцию. Отправляйте SQL без комментариев для массовой вставки.

Что делать вместо этого, если есть возможность

Исправление «на живую» работает — мы проделали это примерно для 130 таблиц и после этого проверили витрину, — но подумайте, что это значит. Восстановление, при котором из каждого первичного ключа выпал  AUTO_INCREMENT , не было избирательным. Возможно, оно также удалило внешние ключи, значения по умолчанию или атрибуты столбцов, которые вы еще не заметили, и никакой объем ALTER для первичных ключей вам об этом не сообщит.

 Если существует чистый дамп с момента до неудачного импорта, восстановите лучше его. Исправление на месте — это то, к чему прибегают, когда такого дампа нет.

Краткая версия за пять секунд

  • Установка плагина завершается ошибкой на таблице ядра с  Duplicate entry '0'  → подозревайте схему, а не плагин.
  • Ошибка перемещается в другую таблицу каждый раз, когда вы исправляете предыдущую → подтверждено, проблема затрагивает всю базу данных.
  •  SET SESSION sql_mode = 'NO_AUTO_VALUE_ON_ZERO'  в том же запросе, что и ALTER.
  • Запускайте через  information_schema , жестко прописывайте имя схемы и проверяйте список перед его выполнением.
  • Предпочитайте восстановление хорошего дампа.

Из опыта поддержки примерно шестидесяти модулей PrestaShop в MEG Venture. Мы потеряли две недели на эту проблему в двух отдельных инцидентах, прежде чем распознали ее паттерн.