Медленный сброс auto_increment - PullRequest
5 голосов
/ 08 ноября 2010

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

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

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. Поэтому, если нам придется скопировать строку в table2, мы получим уникальное нарушение ограничения. У нас уже много кода, и все будет сложно переписать. Корректировка значения автоинкремента кажется наиболее простым способом решения этой проблемы.

Добавлено:

MySQL 5.5 - все то же самое: (

Ответы [ 5 ]

13 голосов
/ 13 ноября 2010

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

пример:

insert into item set id=1097972232-1;

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

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

6 голосов
/ 10 ноября 2010

Это документированная «особенность» 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? Это не то, что вы должны делать регулярно.

3 голосов
/ 13 ноября 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. Поэтому мы немного изменим нашу модель следующим образом:

New model with side table

Мы перейдем к нескольким шагам для переноса нашей модели данных. Операторы 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)
1 голос
/ 13 ноября 2010

Если вам нужно поддерживать уникальные идентификаторы между двумя или более серверами, не используйте этот метод alter table для сброса auto_increment каждый раз. Было бы легче изменить приращение приращения так, чтобы каждый сервер генерировал уникальные идентификаторы без вмешательства. Для двух серверов вы устанавливаете один, чтобы начать с 0, а другой - с 1, с шагом 2 - после этого один будет генерировать четные идентификаторы, а другой - шансы. При использовании трех или более серверов вы просто устанавливаете начальные значения на 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 для каждой таблицы на сервер, а затем они автоматически позаботятся о проблеме уникальности.

0 голосов
/ 08 ноября 2010

Не правда ли:

ALTER TABLE item AUTO_INCREMENT=1;

Источник

...