Медленный сброс auto_increment

У меня много таблиц, и по некоторым причинам мне нужно настроить значение автоматического увеличения для этих таблиц при запуске приложения.

Я пытаюсь сделать это:

mysql> select max(id) from item;
+----------+
| max(id)  |
+----------+
| 97972232 |
+----------+
1 row in set (0.05 sec)

mysql> alter table item auto_increment=1097972232;

В другом сеансе:

afrolov@A1-DB1:~$ mysql -u root -e "show processlist" | grep auto_increment
472196  root    localhost       test    Query   39      copy to tmp table       alter table item auto_increment=1097972232

MySQL начинает перестраивать таблицу! Зачем MySQL нужно это делать? Как я могу избежать перестроения огромных таблиц при настройке значения auto_increment?

MySQL 5.0, InnoDB.
Определение таблицы:

 CREATE TABLE `item` (
      `id` bigint(20) NOT NULL auto_increment,
      `item_res_id` int(11) NOT NULL default '0',
      `stack_count` int(11) NOT NULL default '0',
      `position` int(11) NOT NULL default '0',
      `place` varchar(15) NOT NULL default '',
      `counter` int(11) NOT NULL default '-1',
      `is_bound` tinyint(4) NOT NULL default '0',
      `remove_time` bigint(20) NOT NULL default '-1',
      `rune_res_id` int(11) default NULL,
      `rune_id` bigint(20) default NULL,
      `avatar_id` bigint(20) NOT NULL,
      `rune_slot_res_id` int(11) default NULL,
      `is_cursed` tinyint(4) NOT NULL,
      PRIMARY KEY  (`id`),
      UNIQUE KEY `avatar_id` (`avatar_id`,`place`,`position`),
      UNIQUE KEY `rune_id` (`rune_id`),
      KEY `idx_item_res_id` (`item_res_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=97972233 DEFAULT CHARSET=utf8;

О том, почему я должен это делать. Короче говоря, я хочу обойти проблему mysql innodb, связанную со сбросом значения auto_increment при перезапуске сервера. Иногда мы копируем строки из наших таблиц в другие таблицы, и мы должны сохранять идентификаторы строк без изменений. Когда мы добавим одну строку (например, с id=1) в table1, скопируем строку в table2 , удалим строку из table1 и перезапустим MySQL, то, когда мы создадим новую строку в table1, эта строка тоже получит id=1. Поэтому, если нам придется скопировать строку в таблицу2, мы получим уникальное нарушение ограничения. У нас уже есть много кода, и переписать его весь будет сложно. Настройка значения автоинкремента кажется самым простым способом решить эту проблему.

Добавлен:

MySQL 5.5 - все равно :(


person Andrey Frolov    schedule 08.11.2010    source источник
comment
MySQL 5.0 — это не версия, это целое семейство версий. Укажите все три цифры версии. Если не знаете, сделайте show variables like '%version%'   -  person derobert    schedule 10.11.2010
comment
Кроме того, мне нужно настроить значения автоинкремента... при запуске приложения мне кажется, что вы делаете это неправильно.   -  person derobert    schedule 10.11.2010
comment
Почему на каждом форуме есть парень, который точно знает, что я делаю не так??? :)   -  person Andrey Frolov    schedule 10.11.2010


Ответы (5)


просто добавьте временную запись с желаемым auto_increment_id-1 в каждую таблицу и удалите запись после этого, быстро и легко, но, вероятно, слишком грязно

пример:

insert into item set id=1097972232-1;

после выполнения следующим auto_increment будет 1097972232, что вы и хотели

это может избежать медлительности

person ajreal    schedule 13.11.2010
comment
И затем, вероятно, delete from item where id=1097972232-1, чтобы у вас не было нежелательной строки. Но это работает. Я использовал этот трюк, чтобы позволить активным вставкам продолжаться, оставляя достаточно места для перемещения некоторых строк из другой (например, резервной копии и т. д.). - person Rob Van Dam; 14.11.2010
comment
Вау! Действительно хорошая идея. :) Большое Вам спасибо! У меня будут некоторые проблемы с внешними ключами, но это мелкие проблемы. - person Andrey Frolov; 14.11.2010
comment
На самом деле вставки + отката достаточно. - person Andrey Frolov; 15.11.2010
comment
@Андрей Фролов: бинго! надеюсь, он также не отменит auto_increment? - person ajreal; 15.11.2010
comment
auto_increment не будет откатываться. Я проверил это. Еще раз спасибо :) - person Andrey Frolov; 15.11.2010
comment
@ajreal: у меня такая же проблема, движок innodb, mysql 5.5. Вставьте временное значение, удалите. Затем попробуйте вставить другой, но auto_increment кажется старым. Почему эта штука у меня не работает? - person Sam Ivichuk; 15.04.2013

Это задокументированная «функция» MySQL:

Если вы используете любую опцию ALTER TABLE, кроме RENAME, MySQL всегда создает временную таблицу, даже если данные не обязательно копировать (например, когда вы меняете имя столбца). Для таблиц MyISAM можно ускорить операцию пересоздания индекса (которая является самой медленной частью процесса изменения), установив для системной переменной myisam_sort_buffer_size высокое значение.

http://dev.mysql.com/doc/refman/5.0/en/alter-table.html

MySQL 5.1 и 5.5 поддерживают еще несколько операций изменения таблицы без временной таблицы, но изменение auto_increment не задокументировано как одна из них.

Зачем вообще нужно менять значение auto_increment? Это не то, чем вы должны заниматься постоянно.

person derobert    schedule 10.11.2010
comment
Спасибо за совет. Завтра проверю такое поведение на 5.1 и 5.5. Я обновил первый пост, чтобы ответить, почему я должен это сделать. - person Andrey Frolov; 10.11.2010
comment
Проверял на 5.5. Все так же :( - person Andrey Frolov; 13.11.2010

Не существует простого способа обойти поведение атрибута AUTO_INCREMENT по умолчанию в MySQL, и даже если вы найдете способ, я бы не рекомендовал вам это делать, так как это лучший способ столкнуться с проблемами в краткосрочной перспективе. AUTO_INCREMENT значения не предназначены для корректировки или сброса в производственной среде.

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

Для этого мы будем использовать триггер, который создаст для нас новый идентификатор и назначит его нашей записи элемента. Чтобы это работало, поле id таблицы элементов должно иметь значение null, поэтому мы должны заменить первичный ключ уникальным индексом.

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

Вот несколько примеров скриптов. Допустим, у нас есть две таблицы элементов в нашей базе данных с некоторыми общими строками и некоторыми строками, которые необходимо переместить из первой таблицы во вторую:

CREATE TABLE `item1` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `item_res_id` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `item2` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `item_res_id` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO item1 (item_res_id) VALUES (1);
INSERT INTO item1 (item_res_id) VALUES (2);
INSERT INTO item2 (item_res_id) VALUES (1);

Если мы попытаемся переместить некоторые данные из одной таблицы в другую, а затем перезапустить ваш сервер, мы столкнемся с проблемой сброса значения AUTO_INCREMENT. Поэтому мы немного изменим нашу модель следующим образом:

Новая модель с боковым столиком

Мы выполним несколько шагов для переноса нашей модели данных. Операторы DDL в следующих сценариях миграции были сгенерированы с помощью neXtep Designer IDE.

  • Сначала мы создаем новую таблицу item_keys, которая будет содержать поле AUTO_INCREMENT:
-- Creating table 'item_keys'
CREATE TABLE item_keys ( 
   id BIGINT(20) UNSIGNED NOT NULL
  ,key_ctime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) Engine=InnoDB default charset=utf8;

-- Creating Primary Key constraint 'PRIMARY' on table 'item_keys'
ALTER TABLE item_keys ADD CONSTRAINT PRIMARY KEY (id);
  • Но прежде чем активировать атрибут AUTO_INCREMENT, мы должны вставить существующие идентификаторы в нашу новую таблицу:
-- Initializing item_keys with existing ids
INSERT INTO item_keys (id)
    SELECT i1.id
    FROM item1 i1
        LEFT JOIN item_keys ik ON ik.id = i1.id
    WHERE ik.id IS NULL
;

INSERT INTO item_keys (id)
    SELECT i2.id
    FROM item2 i2
        LEFT JOIN item_keys ik ON ik.id = i2.id
    WHERE ik.id IS NULL
;
  • Теперь мы можем активировать атрибут AUTO_INCREMENT и инициализировать его значение для будущих вставок:
-- Activating auto_increment constraint...
ALTER TABLE item_keys MODIFY id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT;

-- Initializing auto_increment value
SELECT @inc_value := MAX(id) FROM item_keys;
SET @alter_query = CONCAT('ALTER TABLE item_keys AUTO_INCREMENT=',@inc_value); 
PREPARE alter_query FROM @alter_query; 
EXECUTE alter_query; 
DEALLOCATE PREPARE alter_query; 
  • Затем мы можем изменить таблицы item1 и item2, заменив первичный ключ уникальным индексом, и сослаться на первичный ключ таблицы item_keys:
-- De-activating auto_increment constraint...
ALTER TABLE item1 MODIFY id BIGINT(20) UNSIGNED NOT NULL;
-- Dropping constraint 'PRIMARY'...
ALTER TABLE item1 DROP PRIMARY KEY;
ALTER TABLE item1 MODIFY id BIGINT(20) UNSIGNED NULL;
-- Creating index 'item1_uk'...
CREATE UNIQUE INDEX item1_uk ON item1 (id);
-- Creating Foreign Key constraint 'item1_keys_fk' on table 'item1'
ALTER TABLE item1 ADD 
   CONSTRAINT item1_keys_fk FOREIGN KEY item1_keys_fk
      (id) REFERENCES item_keys
      (id)
;
-- De-activating auto_increment constraint...
ALTER TABLE item2 MODIFY id BIGINT(20) UNSIGNED NOT NULL;
-- Dropping constraint 'PRIMARY'...
ALTER TABLE item2 DROP PRIMARY KEY;
ALTER TABLE item2 MODIFY id BIGINT(20) UNSIGNED NULL;
-- Creating index 'item2_uk'...
CREATE UNIQUE INDEX item2_uk ON item2 (id);
-- Creating Foreign Key constraint 'item2_keys_fk' on table 'item2'
ALTER TABLE item2 ADD 
   CONSTRAINT item2_keys_fk FOREIGN KEY item2_keys_fk
      (id) REFERENCES item_keys
      (id)
;
  • Наконец, нам просто нужно создать триггеры, которые будут управлять созданием идентификаторов для нас:
-- Creating trigger 'tr_item1_bi' on table 'item1'...
DELIMITER |;
CREATE TRIGGER tr_item1_bi BEFORE INSERT ON item1
FOR EACH ROW
BEGIN
   IF (NEW.id IS NULL) THEN

        -- If no item id has been specified in the INSERT statement, it
        -- means we want to create a new item. We insert a new record
        -- into the item_keys table to get an item id.
        INSERT INTO item_keys (
            key_ctime
          )
        VALUES (NOW());

        SET NEW.id = LAST_INSERT_ID();
    END IF;
END;
|;
-- Creating trigger 'tr_item2_bi' on table 'item2'...
DELIMITER |;
CREATE TRIGGER tr_item2_bi BEFORE INSERT ON item2
FOR EACH ROW
BEGIN
   IF (NEW.id IS NULL) THEN

        -- If no item id has been specified in the INSERT statement, it
        -- means we want to create a new item. We insert a new record
        -- into the item_keys table to get an item id.
        INSERT INTO item_keys (
            key_ctime
          )
        VALUES (NOW());

        SET NEW.id = LAST_INSERT_ID();
    END IF;
END;
|;

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

--------------
INSERT INTO item2
    SELECT i1.*
    FROM item1 i1
        LEFT JOIN item2 i2
            ON i2.id = i1.id
    WHERE i2.id IS NULL
--------------
Query OK, 1 row affected (0.04 sec)
Records: 1  Duplicates: 0  Warnings: 0

--------------
DELETE FROM item1
--------------
Query OK, 2 rows affected (0.00 sec)

--------------
INSERT INTO item1 (item_res_id) VALUES (3)
--------------
Query OK, 1 row affected (0.00 sec)

--------------
SELECT * FROM item1
--------------

+------+-------------+
| id   | item_res_id |
+------+-------------+
|    3 |           3 |
+------+-------------+
1 row in set (0.00 sec)

--------------
SELECT * FROM item2
--------------

+------+-------------+
| id   | item_res_id |
+------+-------------+
|    1 |           1 |
|    2 |           2 |
+------+-------------+
2 rows in set (0.00 sec)

--------------
SELECT * FROM item_keys
--------------

+----+---------------------+
| id | key_ctime           |
+----+---------------------+
|  1 | 2010-11-14 10:31:21 |
|  2 | 2010-11-14 10:31:21 |
|  3 | 2010-11-14 10:31:46 |
+----+---------------------+
3 rows in set (0.00 sec)
person Bruno Gautier    schedule 13.11.2010
comment
Спасибо за совет. Это хорошая идея, но такой подход вызовет некоторые проблемы с производительностью. - person Andrey Frolov; 14.11.2010
comment
@Эндрю, какой уровень производительности вам нужен для какого типа операций (вставка элементов, массовая вставка элементов, удаление элементов, выбор элементов)? Можете ли вы предоставить некоторые показатели? Вы проводили тест, чтобы сказать, что этого недостаточно? - person Bruno Gautier; 14.11.2010
comment
@ Андрей, я бы понял, если бы вы нашли это решение слишком сложным по сравнению с вставкой временной записи, но я не понимаю аргумент производительности. - person Bruno Gautier; 14.11.2010
comment
Теперь у меня уже есть проблемы с производительностью. И около 60% операторов sql работают с таблицей элементов. Если я добавлю триггер и вставку в таблицу item_keys, мои проблемы только усугубятся. Я не думаю, что бенчмаркинг здесь нужен. Ваш подход тоже очень интересен и, возможно, я воспользуюсь им в следующий раз, когда не смогу изменить приложение. - person Andrey Frolov; 14.11.2010
comment
@Andrew Эндрю, действительно есть накладные расходы (хотя, на мой взгляд, незначительные) с этим решением, и я понимаю, что вы не хотите тратить время на их измерение, особенно когда у вас есть возможность изменить код приложения с помощью простого решения. Я рад, что смог предложить вам другой взгляд на эту проблему. - person Bruno Gautier; 14.11.2010

Если вам нужно поддерживать уникальные идентификаторы между двумя или более серверами, не используйте этот метод изменения таблицы для сброса auto_increment каждый раз. Было бы проще изменить приращение приращения, чтобы каждый сервер генерировал уникальные идентификаторы без вмешательства. Для двух серверов вы устанавливаете один для начала с 0, а другой для начала с 1 с шагом 2 — после этого один будет генерировать четные идентификаторы, а другой — шансы. С 3 или более серверами вы просто устанавливаете начальные значения 0/1/2 с шагом 3, для четырех это 0/1/2/3 с шагом 4 и т. д.

Подробности о настройках на стороне сервера здесь:

http://dev.mysql.com/doc/refman/5.1/en/replication-options-master.html#sysvar_auto_increment_increment

Таким образом, вам нужно сбросить auto_increment только один раз для каждой таблицы на каждом сервере, и после этого они автоматически позаботятся о проблеме уникальности.

person Marc B    schedule 12.11.2010
comment
Это не имеет смысла для меня. Я копирую строки внутри одной базы данных. - person Andrey Frolov; 13.11.2010
comment
Вы также можете установить разные приращения для разных таблиц в одной и той же БД. Если таблица A начинается с 0 и увеличивается на 2, а таблица B начинается с 1 и увеличивается на 2, то вы можете копировать строки вперед и назад по желанию, не беспокоясь о конфликтах клавиш. - person Marc B; 13.11.2010
comment
В моем случае идентификаторы генерируются только в таблице A. - person Andrey Frolov; 13.11.2010

Не так ли:

ALTER TABLE item AUTO_INCREMENT=1;

?

Источник

person Shikiryu    schedule 08.11.2010
comment
Что значит восстановить таблицу? Он просто изменяет значение auto_increment по умолчанию. Ваш стол уже полон? Вы меняете на уже использованный инкремент? Вы используете многоколоночный индекс в этой таблице? - person Shikiryu; 08.11.2010
comment
почти все операторы ALTER TABLE вызывают перестроение таблицы. - person nos; 13.11.2010
comment
@nos и -1: я ответил на его вопрос задолго до того, как он отредактировал мой ответ, теперь он неадекватен, но сначала это помогает. Не был 4 дня. Но спасибо за минус. В любом случае, как вы сказали, почти все ALTER TABLE подойдет, но, по моему опыту, модификация AUTO_INCREMENT не займет много времени (делал это на миллионе без каких-либо проблем), если только вы не используете его на существующая стоимость и т. д. - person Shikiryu; 15.11.2010