Duplicate entry '2147483647' for key 'PRIMARY'

Диагноз

Ну вот и напоролись на ограничение первичного ключа:

Duplicate entry '2147483647' for key 'PRIMARY'

Это сообщение об ошибке из Битрикса в ответ на штатный механизм обновления данных в инфоблоках. Текст SQL ошибки:

MySQL Query Error: 
INSERT INTO b_iblock_element_prop_m45
(IBLOCK_ELEMENT_ID, IBLOCK_PROPERTY_ID, VALUE, VALUE_NUM, DESCRIPTION)
SELECT
111871
,P.ID
,'47585e53-0113-11e0-8255-003048d2334c'
,47584999999999
, '374'
FROM	b_iblock_property P
WHERE	ID=384</font>[Duplicate entry '2147483647' FOR KEY 'PRIMARY']

То же самое из админки при добавлении нового значения свойства:

Вот почему `ID` int(11) NOT NULL AUTO_INCREMENT - зло. А тут еще поле объявлено как целое знаковое (считай, потеряли половину диапазона):

mysql> SHOW CREATE TABLE b_iblock_element_prop_m45\G
*************************** 1. ROW ***************************
       TABLE: b_iblock_element_prop_m45
CREATE TABLE: CREATE TABLE `b_iblock_element_prop_m45` (
  `ID` INT(11) NOT NULL AUTO_INCREMENT,
  `IBLOCK_ELEMENT_ID` INT(11) NOT NULL,
  `IBLOCK_PROPERTY_ID` INT(11) NOT NULL,
  `VALUE` text NOT NULL,
  `VALUE_ENUM` INT(11) DEFAULT NULL,
  `VALUE_NUM` DECIMAL(18,4) DEFAULT NULL,
  `DESCRIPTION` VARCHAR(255) DEFAULT NULL,
  PRIMARY KEY (`ID`),
  KEY `ix_iblock_elem_prop_m45_1` (`IBLOCK_ELEMENT_ID`,`IBLOCK_PROPERTY_ID`),
  KEY `ix_iblock_elem_prop_m45_2` (`IBLOCK_PROPERTY_ID`),
  KEY `ix_iblock_elem_prop_m45_3` (`VALUE_ENUM`,`IBLOCK_PROPERTY_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=2147483647 DEFAULT CHARSET=cp1251
1 ROW IN SET (0.00 sec)

Варианты решения

Решения два: сменить тип первичного ключа на беззнаковый UNSIGNED (проблема откладывается на будущее), либо перенумеровать первичный ключ, так как число записей всего лишь 265422:

mysql> SELECT COUNT(*) FROM b_iblock_element_prop_m45;
+----------+
| COUNT(*) |
+----------+
|   265422 |
+----------+

Смена типа первичного ключа на беззнаковый

Первый вариант сработал, ошибок не замечено:

ALTER TABLE `b_iblock_element_prop_m45` MODIFY `ID` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT;
Одно обновление «откушивает» 60537 ID, по расчетам это хватит еще на 4 года (если 1 обновление в час). На этом можно остановиться.

Перенумерация первичного ключа с исправлением зависимостей

Хорошее решение. Хватит на 8 лет, затем - снова перенумерация. Но так как таблицы могут быть связаны, определим как это сделано.

Поиск зависимостей

Для начала я сделал построчный дамп всех таблиц в отдельные файлы, чтобы удобно было искать:

# PS. Необходимо определить параметры подключения через переменные
for t in $(mysql -NBA -h $DB_host -u $DB_user -D $DB -e 'show tables')
do
    echo "DUMPING TABLE: $t"
    mysqldump --default-character-set=utf8 --extended-insert=FALSE --complete-insert=TRUE -h $DB_host -u $DB_user $DB $t > $DIR/$t.sql
done

Найдем первые и последние 10 ID:

SELECT ID FROM b_iblock_element_prop_m45 ORDER BY ID ASC LIMIT 10;
SELECT ID FROM b_iblock_element_prop_m45 ORDER BY ID DESC LIMIT 10;

Затем поищем в каких таблицах есть эти ID:

grep -r 1837653803
grep -r 1837653812
# и так далее

Я понял, что связанных таблиц две: b_iblock_element_prop_m45 и b_iblock_element_prop_s45 - в ней ID сохранен как serialized:

{i:0;s:10:\"1837653803\";i:1;s:10:\"1837653804\";i:2;s:10:\"1837653805\";i:3;s:10:\"1837653806\"

То, что связка хранится как serialized, усложняет задачу: нельзя обойтись только средствами SQL.

Размеры таблиц:

SELECT TABLE_NAME AS `Table`,
round(((data_length + index_length) / 1024 / 1024), 2) `Size in MB`
FROM information_schema.TABLES
WHERE 
TABLE_NAME IN ('b_iblock_element_prop_m45', 'b_iblock_element_prop_s45')
AND table_schema = "my_cool_db";
+---------------------------+------------+
| TABLE                     | SIZE IN MB |
+---------------------------+------------+
| b_iblock_element_prop_m45 |      37.09 |
| b_iblock_element_prop_s45 |      19.55 |
+---------------------------+------------+

Таблицы небольшие, поэтому сделаю скрипт, который сделает две временные таблицы, перенумерует ID «как надо», и заменит существующие данные.

Для обкатки на тестовом сайте делаем дамп, который будем поднимать в случае провала:

mysqldump my_cool_db b_iblock_element_prop_m45 b_iblock_element_prop_s45 > prop-45.sql;

Скрипт перенумерации ID

Найденные зависимости следует подставить в скрипт перенумерации ID.

Заняло 30 секунд на таблицах с количеством записей: 265422 (перенумерация, где вылез лимит) и 10987 (исправление ID).

Печать/экспорт