【问题标题】:Slow auto_increment reset缓慢的 auto_increment 重置
【发布时间】:2011-05-06 16:58:02
【问题描述】:

我有很多表,由于某些原因,我需要在应用程序启动时调整这些表的自动增量值。

我尝试这样做:

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;

关于为什么我必须这样做。长话短说,我想解决有关在服务器重新启动时重置 auto_increment 值的 mysql innodb 问题。有时我们将行从我们的表复制到另一个表,我们必须保持行 id 不变。当我们向 table1 添加一行(例如 id=1)时,将行复制到 table2,从 table1 中删除行并重新启动 MySQL,然后当我们在 table1 中创建新的一行时,该行也会得到 id=1。因此,如果我们必须将行复制到 table2,我们就会违反唯一约束。我们已经有很多代码,很难全部重写。调整自动增量值似乎是解决此问题的最简单方法。

添加:

MySQL 5.5 - 都一样 :(

【问题讨论】:

  • MySQL 5.0 不是一个版本,它是一个完整的版本家族。请提供版本的所有三位数字。如果你不知道,请show variables like '%version%'
  • 另外,我需要在应用程序启动时调整自动增量值...让我印象深刻,因为你做错了
  • 为什么每个论坛上都有一个人,他完全知道我做错了??? :)

标签: mysql innodb auto-increment


【解决方案1】:

只需将需要auto_increment_id-1 的临时记录添加到每个表中, 然后删除记录,快速简单,但可能太脏了

示例:

insert into item set id=1097972232-1;

执行后,下一个 auto_increment 将是 1097972232,这是你想要的

这样可以避免缓慢

【讨论】:

  • 然后可能是delete from item where id=1097972232-1,所以你没有垃圾行。但这确实有效。我已经使用这个技巧来允许活动插入继续进行,同时留出足够的空间从另一行移入某些行(例如备份等)。
  • 哇!真是个好主意。 :) 非常感谢!我会遇到一些外键问题,但这是小问题。
  • 其实插入+回滚就够了。
  • @Andrew Frolov:宾果游戏!希望它也不会回滚 auto_increment?
  • auto_increment 不会回滚。我检查了这个。再次感谢你:)
【解决方案2】:

这是记录在案的 MySQL“特性”:

如果您使用除 RENAME 之外的任何 ALTER TABLE 选项,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 的值呢?这不是你应该经常做的事情。

【讨论】:

  • 感谢您的建议。明天我将在 5.1 和 5.5 上检查这种行为。我更新了第一篇文章来回答为什么我必须这样做。
  • 在 5.5 上检查。都一样:(
【解决方案3】:

没有简单的方法可以绕过 MySQL 中的 AUTO_INCREMENT 属性默认行为,即使您找到了方法,我也不建议您这样做,因为这是在 MySQL 中遇到问题的最佳方法短期的。 AUTO_INCREMENT 值不应在生产环境中调整或重置。

解决您的问题的一种可能方法是稍微非规范化您的模型。这个想法是将AUTO_INCREMENT 字段移动到您不必复制或删除行的边表中。然后,您所要做的就是在创建新项目时从该边表中获取新的 id 值,并在将行从一个表复制到另一个表时保留现有的 id 值。

为了实现这一点,我们将使用一个触发器,该触发器将为我们创建一个新的 id 并将其分配给我们的项目记录。 item 表的 id 字段必须可以为空才能工作,因此我们必须用唯一索引替换主键。

此模型更改将对您的应用程序完全透明,因此您将无需对应用程序代码进行任何更改

这里有一些示例脚本。假设我们的数据库中有两个项目表,其中有一些常见的行,还有一些需要从第一个表移动到第二个表的行:

创建表`item1`( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `item_res_id` int(11) NOT NULL DEFAULT '0', 主键(`id`) ) 引擎=InnoDB 默认字符集=utf8; 创建表`item2`( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `item_res_id` int(11) NOT NULL DEFAULT '0', 主键(`id`) ) 引擎=InnoDB 默认字符集=utf8; 插入项目 1 (item_res_id) 值 (1); 插入项目 1 (item_res_id) 值 (2); 插入项目 2 (item_res_id) 值 (1);

如果我们尝试将一些数据从一个表移动到另一个表,然后重新启动您的服务器,我们将遇到AUTO_INCREMENT 值重置的问题。所以我们将模型稍作修改如下:

我们将分几个步骤来迁移我们的数据模型。以下迁移脚本中的 DDL 语句是使用neXtep Designer IDE 生成的。

  • 首先我们创建一个新的 item_keys 表来保存AUTO_INCREMENT 字段:
-- 创建表 'item_keys' 创建表 item_keys ( id BIGINT(20) 无符号非空 ,key_ctime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ) 引擎=InnoDB 默认字符集=utf8; -- 在表 'item_keys' 上创建主键约束 'PRIMARY' ALTER TABLE item_keys 添加约束主键(id);
  • 但在激活AUTO_INCREMENT 属性之前,我们必须将现有ID 插入到我们的新表中:
-- 使用现有 id 初始化 item_keys 插入到 item_keys (id) 选择 i1.id 来自项目 1 i1 左连接 item_keys ik ON ik.id = i1.id 哪里 ik.id 为空 ; 插入到 item_keys (id) 选择 i2.id FROM item2 i2 左连接 item_keys ik ON ik.id = i2.id 哪里 ik.id 为空 ;
  • 我们现在可以激活AUTO_INCREMENT 属性,并为以后的插入初始化它的值:
-- 激活 auto_increment 约束... ALTER TABLE item_keys MODIFY id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT; -- 初始化 auto_increment 值 SELECT @inc_value := MAX(id) FROM item_keys; SET @alter_query = CONCAT('ALTER TABLE item_keys AUTO_INCREMENT=',@inc_value); 从@alter_query 准备alter_query; 执行更改查询; DEALLOCATE PREPARE alter_query;
  • 然后我们可以修改item1item2表,用唯一索引替换主键,并引用item_keys表的主键:
-- 取消激活 auto_increment 约束... ALTER TABLE item1 MODIFY id BIGINT(20) UNSIGNED NOT NULL; -- 删除约束 'PRIMARY'... ALTER TABLE item1 DROP PRIMARY KEY; ALTER TABLE item1 MODIFY id BIGINT(20) UNSIGNED NULL; -- 创建索引 'item1_uk'... 在 item1 (id) 上创建唯一索引 item1_uk; -- 在表 'item1' 上创建外键约束 'item1_keys_fk' ALTER TABLE item1 添加 约束 item1_keys_fk 外键 item1_keys_fk (id) 参考 item_keys (ID) ; -- 取消激活 auto_increment 约束... ALTER TABLE item2 MODIFY id BIGINT(20) UNSIGNED NOT NULL; -- 删除约束 'PRIMARY'... ALTER TABLE item2 DROP PRIMARY KEY; ALTER TABLE item2 MODIFY id BIGINT(20) UNSIGNED NULL; -- 创建索引 'item2_uk'... 在 item2 (id) 上创建唯一索引 item2_uk; -- 在表 'item2' 上创建外键约束 'item2_keys_fk' ALTER TABLE item2 添加 约束 item2_keys_fk 外键 item2_keys_fk (id) 参考 item_keys (ID) ;
  • 最后,我们只需创建将为我们管理 ID 创建的触发器:
-- 在表 'item1' 上创建触发器 'tr_item1_bi'... 分隔符 |; 在插入 item1 之前创建触发器 tr_item1_bi 每一行 开始 如果(NEW.id 为空)那么 -- 如果 INSERT 语句中没有指定项目 id,则 -- 表示我们要创建一个新项目。我们插入一条新记录 -- 进入 item_keys 表以获取项目 ID。 插入到 item_keys ( key_ctime ) 值(现在()); SET NEW.id = LAST_INSERT_ID(); 万一; 结尾; |; -- 在表 'item2' 上创建触发器 'tr_item2_bi'... 分隔符 |; 在插入 item2 之前创建触发器 tr_item2_bi 每一行 开始 如果(NEW.id 为空)那么 -- 如果 INSERT 语句中没有指定项目 id,则 -- 表示我们要创建一个新项目。我们插入一条新记录 -- 进入 item_keys 表以获取项目 ID。 插入到 item_keys ( key_ctime ) 值(现在()); SET NEW.id = LAST_INSERT_ID(); 万一; 结尾; |;

现在我们可以将数据从一个表移动到另一个表,保持 id 不变,如果我们重新启动服务器,item_keys 中的 AUTO_INCREMENT 值将保持不变。

-------------- 插入第 2 项 选择 i1.* 来自项目 1 i1 左连接 item2 i2 开 i2.id = i1.id i2.id 为空 -------------- 查询正常,1 行受影响(0.04 秒) 记录:1 重复:0 警告:0 -------------- 从项目 1 中删除 -------------- 查询正常,2 行受影响(0.00 秒) -------------- 插入项目 1 (item_res_id) 值 (3) -------------- 查询正常,1 行受影响(0.00 秒) -------------- 从项目 1 中选择 * -------------- +--------+-------------+ |编号 | item_res_id | +--------+-------------+ | 3 | 3 | +--------+-------------+ 一组中的 1 行(0.00 秒) -------------- 从项目 2 中选择 * -------------- +--------+-------------+ |编号 | item_res_id | +--------+-------------+ | 1 | 1 | | 2 | 2 | +--------+-------------+ 2 行(0.00 秒) -------------- 选择 * 从 item_keys -------------- +----+----------+ |编号 | key_ctime | +----+----------+ | 1 | 2010-11-14 10:31:21 | | 2 | 2010-11-14 10:31:21 | | 3 | 2010-11-14 10:31:46 | +----+----------+ 3 行一组(0.00 秒)

【讨论】:

  • 感谢您的建议。这是个好主意,但是这种方法会导致一些性能问题。
  • @Andrew,什么类型的操作(项目插入、项目批量插入、项目删除、项目选择)需要什么级别的性能?你能提供一些指标吗?你有没有运行过一个基准测试,所以你可以说这还不够好?
  • @Andrew,如果你觉得这个解决方案与临时记录插入相比过于复杂,我会理解,但我不明白性能的论点。
  • 现在我已经遇到了性能问题。大约 60% 的 sql 语句适用于 items 表。如果我在 item_keys 表中添加触发器和插入,我的麻烦只会变得更大。我认为这里不需要基准测试。你的方法也很有趣,下次我可能会使用它,当我无法更改应用程序时。
  • @Andrew,这个解决方案确实存在开销(尽管在我看来很小),我知道你不想花时间去衡量它,特别是当你可以选择改变时带有简单解决方案的应用程序代码。很高兴我能为您带来关于这个问题的不同观点。
【解决方案4】:

如果您需要在两台或多台服务器之间维护唯一 ID,请不要使用这种每次都重置 auto_increment 的 alter table 方法。更改增量的增量会更容易,这样每个服务器都会在没有干预的情况下生成唯一的 ID。对于两台服务器,您设置一台从 0 开始,一台从 1 开始,增量为 2 - 之后一台将生成偶数 ID,另一台将生成赔率。对于 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,然后它们会自动处理唯一性问题。

【讨论】:

  • 这对我来说没有意义。我正在一个数据库中复制行。
  • 您也可以在同一个数据库中的不同表上设置不同的增量。如果表 A 从 0 开始并递增 2,表 B 从 1 开始并递增 2,那么您可以随意来回复制行,而不必担心键冲突。
  • 在我的情况下,ID 仅在表 A 中生成。
【解决方案5】:

不是吗:

ALTER TABLE item AUTO_INCREMENT=1;

?

Source

【讨论】:

  • 你是什么意思“重建”表?它只是更改了 auto_increment 默认值。你的桌子已经“满”了吗?您是否要更改已使用的增量?您是否在此表中使用了多列索引?
  • 几乎所有的 ALTER TABLE 语句都会导致表的重建。
  • @nos 和 -1 :我在他的编辑做出我的回答之前回答了他的问题 loonnng,现在,不够充分,但起初,它有帮助。 4天没来。但感谢您的反对。无论如何,正如你所说的,几乎所有ALTER TABLE 都可以,但根据我的经验,修改AUTO_INCREMENT 不会花很长时间(一百万没有任何问题)除非你将其用于现有值等。
猜你喜欢
  • 2013-03-22
  • 2015-11-10
  • 2013-12-11
  • 2013-12-22
  • 1970-01-01
  • 2017-04-14
相关资源
最近更新 更多